diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f886600..e0ba133f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ All notable changes to this project will be documented in this file. +## [2.1.2] - 9/2026 + +### Fixed + +- **Home Assistant's warning about a deprecated device lookup is resolved** — the integration, its card and its dashboard now find devices the way Home + Assistant 2026.8 asks, before the old ways stop working in 2027.8. +- **A SPAN Panel card set up before Home Assistant 2026.8 works again, with nothing to change** — Home Assistant gave the panel a new device ID during that + upgrade if a helper, such as a utility meter, was attached to it. A card still holding the old ID now finds the panel again: its battery, charger and other + devices, its monitoring and its circuits' areas all come back, where it showed no devices or an intermittent "not loaded" error. +- **The card no longer shows an empty tile for the Microgrid Interconnect** — a device with nothing to display is now left off the card rather than drawn as a + bare header and settings icon. + ## [2.1.1] - 8/2026 ### Added diff --git a/custom_components/span_panel/additions.py b/custom_components/span_panel/additions.py index d1349109..4957f7fc 100644 --- a/custom_components/span_panel/additions.py +++ b/custom_components/span_panel/additions.py @@ -395,7 +395,9 @@ def _sub_device_name(devices: dr.DeviceRegistry, registry_entry: er.RegistryEntr if registry_entry.device_id is None: return None device = devices.async_get(registry_entry.device_id) - if device is None or device.via_device_id is None: + # A child device (Home Assistant 2026.9+) hangs off nothing by + # `via_device_id`, and SPAN registers none, so it names no sub-device. + if not isinstance(device, dr.DeviceEntry) or device.via_device_id is None: return None return device.name_by_user or device.name diff --git a/custom_components/span_panel/adoption.py b/custom_components/span_panel/adoption.py index a2a4060d..8f12388c 100644 --- a/custom_components/span_panel/adoption.py +++ b/custom_components/span_panel/adoption.py @@ -247,7 +247,13 @@ def adopted_identifier(panel_serial: str, anchor: str) -> str: return f"{panel_serial}_{ADOPTED_IDENTIFIER_TOKEN}_{anchor}" -def resolve_identifier(registry: DeviceRegistry, panel_serial: str, device: AdoptedDevice) -> str: +def resolve_identifier( + registry: DeviceRegistry, + panel_serial: str, + device: AdoptedDevice, + *, + config_entry_id: str, +) -> str: """Return the identifier this install already uses for this device, or a new one. **An adopted device freezes its identity anchor at first sighting.** Both @@ -267,12 +273,22 @@ def resolve_identifier(registry: DeviceRegistry, panel_serial: str, device: Adop The registry is the memory, so this needs no new persistence: a device that exists was adopted before, and one that does not is being adopted now. + + That memory is read within `config_entry_id`, never across every entry. + Identifiers are unique inside a config entry and nowhere else, so the unscoped + question can be answered by a device another entry owns -- and this panel's + device would then be frozen onto a stranger's identifier, permanently, since + the freeze is by design irreversible. Home Assistant deprecated the unscoped + lookup for that ambiguity and stops answering it in 2027.8. """ for candidate in (device.device_id, device.serial_number): if candidate is None: continue identifier = adopted_identifier(panel_serial, candidate) - if registry.async_get_device(identifiers={(DOMAIN, identifier)}) is not None: + if ( + registry.async_get_device_by_identifier((DOMAIN, identifier), config_entry_id) + is not None + ): return identifier return adopted_identifier(panel_serial, adopted_anchor(device)) @@ -403,7 +419,9 @@ def async_register_adopted_devices( """ registry = dr.async_get(hass) for device in snapshot.adopted_devices: - identifier = resolve_identifier(registry, snapshot.serial_number, device) + identifier = resolve_identifier( + registry, snapshot.serial_number, device, config_entry_id=entry_id + ) registry.async_get_or_create( config_entry_id=entry_id, **adopted_device_info(identifier, device, panel_device_id=panel_device_id), @@ -855,6 +873,7 @@ def create_adopted_sensors( snapshot: SpanPanelSnapshot, registry: DeviceRegistry, *, + config_entry_id: str, panel_device_id: str, overlay: CurationOverlay, ) -> list[AdoptedSensor]: @@ -869,6 +888,7 @@ def create_adopted_sensors( snapshot, registry, Platform.SENSOR, + config_entry_id=config_entry_id, panel_device_id=panel_device_id, overlay=overlay, ) @@ -879,6 +899,7 @@ def create_adopted_binary_sensors( snapshot: SpanPanelSnapshot, registry: DeviceRegistry, *, + config_entry_id: str, panel_device_id: str, overlay: CurationOverlay, ) -> list[AdoptedBinarySensor]: @@ -889,6 +910,7 @@ def create_adopted_binary_sensors( snapshot, registry, Platform.BINARY_SENSOR, + config_entry_id=config_entry_id, panel_device_id=panel_device_id, overlay=overlay, ) @@ -899,6 +921,7 @@ def create_adopted_switches( snapshot: SpanPanelSnapshot, registry: DeviceRegistry, *, + config_entry_id: str, panel_device_id: str, overlay: CurationOverlay, ) -> list[AdoptedSwitch]: @@ -909,6 +932,7 @@ def create_adopted_switches( snapshot, registry, Platform.SWITCH, + config_entry_id=config_entry_id, panel_device_id=panel_device_id, overlay=overlay, ) @@ -919,6 +943,7 @@ def create_adopted_selects( snapshot: SpanPanelSnapshot, registry: DeviceRegistry, *, + config_entry_id: str, panel_device_id: str, overlay: CurationOverlay, ) -> list[AdoptedSelect]: @@ -929,6 +954,7 @@ def create_adopted_selects( snapshot, registry, Platform.SELECT, + config_entry_id=config_entry_id, panel_device_id=panel_device_id, overlay=overlay, ) @@ -939,6 +965,7 @@ def create_adopted_numbers( snapshot: SpanPanelSnapshot, registry: DeviceRegistry, *, + config_entry_id: str, panel_device_id: str, overlay: CurationOverlay, ) -> list[AdoptedNumber]: @@ -949,6 +976,7 @@ def create_adopted_numbers( snapshot, registry, Platform.NUMBER, + config_entry_id=config_entry_id, panel_device_id=panel_device_id, overlay=overlay, ) @@ -961,6 +989,7 @@ def _create[AdoptedT: AdoptedEntity]( registry: DeviceRegistry, platform: Platform, *, + config_entry_id: str, panel_device_id: str, overlay: CurationOverlay, ) -> list[AdoptedT]: @@ -1006,7 +1035,7 @@ def _create[AdoptedT: AdoptedEntity]( """ built: list[AdoptedT] = [] claimed: dict[str, str] = {} - for device, identifier in _adopted(snapshot, registry): + for device, identifier in _adopted(snapshot, registry, config_entry_id): for declaration in sorted(device.properties, key=lambda row: row.path): if classify(declaration) is not platform: continue @@ -1043,10 +1072,15 @@ def _create[AdoptedT: AdoptedEntity]( def _adopted( - snapshot: SpanPanelSnapshot, registry: DeviceRegistry + snapshot: SpanPanelSnapshot, registry: DeviceRegistry, config_entry_id: str ) -> list[tuple[AdoptedDevice, str]]: """Each adopted device paired with the identifier this install uses for it.""" return [ - (device, resolve_identifier(registry, snapshot.serial_number, device)) + ( + device, + resolve_identifier( + registry, snapshot.serial_number, device, config_entry_id=config_entry_id + ), + ) for device in snapshot.adopted_devices ] diff --git a/custom_components/span_panel/binary_sensor.py b/custom_components/span_panel/binary_sensor.py index f260c689..cdd3dad6 100644 --- a/custom_components/span_panel/binary_sensor.py +++ b/custom_components/span_panel/binary_sensor.py @@ -690,6 +690,7 @@ async def async_setup_entry( coordinator, snapshot, dr.async_get(hass), + config_entry_id=config_entry.entry_id, panel_device_id=config_entry.runtime_data.panel_device_id, overlay=config_entry.runtime_data.curation, ), @@ -702,6 +703,7 @@ async def async_setup_entry( snapshot, dr.async_get(hass), er.async_get(hass), + config_entry_id=config_entry.entry_id, overlay=config_entry.runtime_data.curation, ), ] diff --git a/custom_components/span_panel/extension.py b/custom_components/span_panel/extension.py index 5c949a20..dc6e6b9a 100644 --- a/custom_components/span_panel/extension.py +++ b/custom_components/span_panel/extension.py @@ -526,6 +526,7 @@ def create_extension_sensors( device_registry: DeviceRegistry, entity_registry: EntityRegistry, *, + config_entry_id: str, overlay: CurationOverlay, ) -> list[ExtensionSensor]: """Every extension property that is not a declared boolean.""" @@ -536,6 +537,7 @@ def create_extension_sensors( device_registry, entity_registry, Platform.SENSOR, + config_entry_id=config_entry_id, overlay=overlay, ) @@ -546,6 +548,7 @@ def create_extension_binary_sensors( device_registry: DeviceRegistry, entity_registry: EntityRegistry, *, + config_entry_id: str, overlay: CurationOverlay, ) -> list[ExtensionBinarySensor]: """Every extension property declared `boolean`.""" @@ -556,6 +559,7 @@ def create_extension_binary_sensors( device_registry, entity_registry, Platform.BINARY_SENSOR, + config_entry_id=config_entry_id, overlay=overlay, ) @@ -568,6 +572,7 @@ def _create[ExtensionT: ExtensionEntity]( entity_registry: EntityRegistry, platform: Platform, *, + config_entry_id: str, overlay: CurationOverlay, ) -> list[ExtensionT]: """Build one platform's share of the extension properties. @@ -592,7 +597,9 @@ def _create[ExtensionT: ExtensionEntity]( type system holding the two to one contract, not a case that occurs. """ built: list[ExtensionT] = [] - for row, unique_id, device_identifier in adoptable(snapshot, device_registry, entity_registry): + for row, unique_id, device_identifier in adoptable( + snapshot, device_registry, entity_registry, config_entry_id=config_entry_id + ): if resolve_platform(entity_registry, unique_id, row.datatype) is not platform: continue key = extension_curation_key(row.subject, row.path) @@ -623,14 +630,16 @@ def adoptable( snapshot: SpanPanelSnapshot, device_registry: DeviceRegistry, entity_registry: EntityRegistry, + *, + config_entry_id: str, ) -> list[tuple[ExtensionProperty, str, str]]: """Every extension property that can become an entity, with its id and card. Three reasons a declared property is declined here, all of them stated rather - than silent: its subject resolves to no device card, its card is not in the - registry yet, or its address is outside the Homie charset. The first two are - ordinary states on a setup that raced a capability -- the entity appears on - the next reload, as capability-gated platforms already do. + than silent: its subject resolves to no device card, its card is not in this + entry's registry yet, or its address is outside the Homie charset. The first + two are ordinary states on a setup that raced a capability -- the entity + appears on the next reload, as capability-gated platforms already do. **An id the registry already holds is never displaced by the cap.** The cap admits rows in the order the adapter emitted them, and that order tracks the @@ -643,13 +652,15 @@ def adoptable( everything already registered is admitted first, and the cap applies only to what is new. """ - return _partition(snapshot, device_registry, entity_registry)[0] + return _partition(snapshot, device_registry, entity_registry, config_entry_id)[0] def declined_extensions( snapshot: SpanPanelSnapshot, device_registry: DeviceRegistry, entity_registry: EntityRegistry, + *, + config_entry_id: str, ) -> dict[str, int]: """How many properties each wire device declared beyond the cap. @@ -658,22 +669,42 @@ def declined_extensions( partition, and a warning per platform would double-count in the log while saying nothing new. """ - return _partition(snapshot, device_registry, entity_registry)[1] + return _partition(snapshot, device_registry, entity_registry, config_entry_id)[1] def _partition( snapshot: SpanPanelSnapshot, device_registry: DeviceRegistry, entity_registry: EntityRegistry, + config_entry_id: str, ) -> tuple[list[tuple[ExtensionProperty, str, str]], dict[str, int]]: - """Split the declared properties into what is adopted and what the cap declined.""" + """Split the declared properties into what is adopted and what the cap declined. + + The card is looked up within `config_entry_id` rather than across every entry, + for the reason `util.py` gives at its own head: identifiers are unique inside a + config entry and nowhere else. The unscoped question could be answered by a + device another entry owns, and the row would then be admitted as though its + card existed. It would not land on that device -- the registry files an + entity's device within the entity's own entry -- but on a card minted for it + here, carrying nothing but the identifier: the card of its own an extension + entity must never mint, reached around the deferral that exists to prevent it. + + A second SPAN entry cannot hold this panel's identifiers today, because the + config flow keys entries on the serial every identifier embeds. That is a + property of the flow rather than of the registry, and the lookup does not + rest on it. Home Assistant reports the unscoped lookup as deprecated for this + ambiguity and says it stops working in 2027.8. + """ known: list[tuple[ExtensionProperty, str, str]] = [] fresh: list[tuple[ExtensionProperty, str, str]] = [] for row in snapshot.extension_properties: identifier = extension_device_identifier(snapshot.serial_number, row.subject) if identifier is None: continue - if device_registry.async_get_device(identifiers={(DOMAIN, identifier)}) is None: + if ( + device_registry.async_get_device_by_identifier((DOMAIN, identifier), config_entry_id) + is None + ): _LOGGER.debug( "Extension property %s has no registered device for %s yet; deferred to the next reload", row.path, @@ -755,7 +786,9 @@ async def async_notice_declined_extensions( readings declined on the same one -- is news the user is told about, while a translation change is not. """ - declined = declined_extensions(snapshot, device_registry, entity_registry) + declined = declined_extensions( + snapshot, device_registry, entity_registry, config_entry_id=entry.entry_id + ) if not declined: return rendered = ", ".join(f"{key} ({count})" for key, count in sorted(declined.items())) diff --git a/custom_components/span_panel/frontend/dist/span-panel-card.js b/custom_components/span_panel/frontend/dist/span-panel-card.js index 3eee3e88..09b1cfee 100644 --- a/custom_components/span_panel/frontend/dist/span-panel-card.js +++ b/custom_components/span_panel/frontend/dist/span-panel-card.js @@ -55,7 +55,7 @@ var Ga={},qa={};var ja,Xa=function(){function t(t,e,n){var i=this;this._sleepAft width: 100%; height: 100%; } - `,b([Ot({attribute:!1})],Lk.prototype,"options",void 0),b([Ot({attribute:!1})],Lk.prototype,"data",void 0),b([Ot({type:String})],Lk.prototype,"height",void 0);try{customElements.get("span-chart")||customElements.define("span-chart",Lk)}catch{}function Ek(t,e,n,i,r,a,s,l,c){const{options:u,series:d}=function(t,e,n,i,r,a=!1){n||(n=g[o]);const s=i?"140, 160, 220":"77, 217, 175",l=`rgb(${s})`,c=Date.now(),u=c-e,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,h=(t??[]).filter(t=>t.time>=u).map(t=>[t.time,Math.abs(t.value)]),p=[{type:"line",data:h,showSymbol:!1,smooth:!1,...a?{}:{step:"end"},lineStyle:{width:1.5,color:l},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.18)`},{offset:1,color:`rgba(${s}, 0.18)`}]}},itemStyle:{color:l}}],f=h.length>0?function(t){let e=0;for(const n of t)n[1]>e&&(e=n[1]);return e}(h):0,v={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:f<10?t=>0===t?"0":t.toFixed(1):t=>n.format(t)},splitLine:{lineStyle:{opacity:.15}}};d?(v.min=n.fixedMin,v.max=n.fixedMax):f<1&&(v.min=0,v.max=1),r&&"current"===n.entityRole&&(v.min=0,v.max=Math.ceil(1.25*r),p.push({type:"line",data:[[u,.8*r],[c,.8*r]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),p.push({type:"line",data:[[u,r],[c,r]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}}));const m={xAxis:{type:"time",min:u,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:v,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:t=>{if(!t||0===t.length)return"";const e=t[0],i=new Date(e.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"}),r=parseFloat(e.value[1].toFixed(2));return`
${i}
${n.format(r)} ${n.unit(r)}
`}},animation:!1};return{options:m,series:p}}(n,i,r,a,l,c),h=s??120;t.style.minHeight=h+"px";let p=t.querySelector("span-chart");p||(p=document.createElement("span-chart"),p.style.display="block",p.style.width="100%",t.innerHTML="",t.appendChild(p));const f=t.clientHeight;p.height=(f>0?f:h)+"px",p.options=u,p.data=d}function Ok(t){return"function"==typeof globalThis.CSS?.escape?CSS.escape(t):t.replace(/["\\]/g,"\\$&")}function zk(t,e,n,i,r){const o=t.querySelector(".panel-stats");o&&function(t,e,n,i,r){const o="current"===(i.chart_metric||"power"),a=t.querySelector(".stat-consumption .stat-value"),s=t.querySelector(".stat-consumption .stat-unit");if(o){const t=n.panel_entities?.site_power,i=t?e.states[t]:null,r=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(r)?Math.abs(r).toFixed(1):"--"),s&&(s.textContent="A")}else{let t=r;const i=n.panel_entities?.site_power;if(i){const n=e.states[i];n&&(t=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=Gt(t)),s&&(s.textContent="kW")}const l=t.querySelector(".stat-upstream .stat-value"),c=t.querySelector(".stat-upstream .stat-unit");if(l){const t=n.panel_entities?.current_power,i=t?e.states[t]:null;if(o){const t=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",c&&(c.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=Gt(t),c&&(c.textContent="kW")}}const u=t.querySelector(".stat-downstream .stat-value"),d=t.querySelector(".stat-downstream .stat-unit");if(u){const t=n.panel_entities?.feedthrough_power,i=t?e.states[t]:null;if(o){const t=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",d&&(d.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;u.textContent=Gt(t),d&&(d.textContent="kW")}}const h=t.querySelector(".stat-solar .stat-value"),p=t.querySelector(".stat-solar .stat-unit");if(h){const t=n.panel_entities?.pv_power,i=t?e.states[t]:null;if(o){const t=i?parseFloat(i.attributes?.amperage):NaN;h.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",p&&(p.textContent="A")}else{if(i){const t=Math.abs(parseFloat(i.state)||0);h.textContent=Gt(t)}else h.textContent="--";p&&(p.textContent="kW")}}const f=t.querySelector(".stat-battery .stat-value");if(f){const t=n.panel_entities?.battery_level,i=t?e.states[t]:null;i&&(f.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const g=t.querySelector(".stat-grid-state .stat-value");if(g){const t=n.panel_entities?.dsm_state,i=t?e.states[t]:null;g.textContent=i?e.formatEntityState?.(i)||i.state:"--"}}(o,e,n,i,r)}class Nk{get errorStore(){return this._errorStore}set errorStore(t){this._errorStore=t,this._retry=t?new Qt(t):null}constructor(){this._errorStore=null,this._retry=null,this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(t,e){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const n={};e&&(n.config_entry_id=e);const r={type:"call_service",domain:l,service:"get_graph_settings",service_data:n,return_response:!0},o=this._retry?await this._retry.callWS(t,r,{errorId:"fetch:graph_settings",errorMessage:i("error.graph_settings_failed")}):await t.callWS(r);this._settings=o?.response??null,this._lastFetch=Date.now()}catch(t){console.warn("SPAN Panel: graph settings fetch failed",t),this._settings=null,this._retry||this._errorStore?.add({key:"fetch:graph_settings",level:"warning",message:i("error.graph_settings_failed"),persistent:!1})}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}function Rk(t,e){if(!t)return a;const n=t.circuits?.[e];return n?.has_override?n.horizon:t.global_horizon??a}function Hk(t,e){if(!t)return a;const n=t.sub_devices?.[e];return n?.has_override?n.horizon:t.global_horizon??a}class Bk{constructor(){this.powerHistory=new Map,this.horizonMap=new Map,this.subDeviceHorizonMap=new Map,this.monitoringCache=new Jt,this.monitoringMultiCache=new te,this.graphSettingsCache=new Nk,this._errorStore=null,this._hass=null,this._topology=null,this._config=null,this._configEntryId=null,this._favRefs=null,this._perPanelInfo=new Map,this._panelFavorites=null,this._showMonitoring=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._resizeObserver=null,this._lastWidth=0,this._resizeDebounce=null}get errorStore(){return this._errorStore}set errorStore(t){this._errorStore=t,this.monitoringCache.errorStore=t,this.graphSettingsCache.errorStore=t,this.monitoringMultiCache.errorStore=t}get hass(){return this._hass}set hass(t){this._hass=t}get topology(){return this._topology}get config(){return this._config}set showMonitoring(t){this._showMonitoring=t}init(t,e,n,i){this._topology=t,this._config=e,this._hass=n,this._configEntryId=i}setFavoriteRefs(t){this._favRefs=t}clearFavoriteRefs(){this._favRefs=null}setPanelFavorites(t){this._panelFavorites=t}setFavoritesPerPanelInfo(t){this._perPanelInfo=t??new Map}get _inFavoritesView(){return null!==this._favRefs}setConfig(t){this._config=t}buildHorizonMaps(t){if(this.horizonMap.clear(),this.subDeviceHorizonMap.clear(),t&&this._topology?.circuits)for(const e of Object.keys(this._topology.circuits))this.horizonMap.set(e,Rk(t,e));if(t&&this._topology?.sub_devices)for(const e of Object.keys(this._topology.sub_devices))this.subDeviceHorizonMap.set(e,Hk(t,e))}async fetchAndBuildHorizonMaps(){try{this._favRefs?await this._buildFavoritesHorizonMaps():(await this.graphSettingsCache.fetch(this._hass,this._configEntryId),this.buildHorizonMaps(this.graphSettingsCache.settings))}catch(t){console.warn("SPAN Panel: graph settings fetch failed",t),this.graphSettingsCache.errorStore||this._errorStore?.add({key:"fetch:graph_settings",level:"warning",message:i("error.graph_settings_failed"),persistent:!1})}}async fetchMergedMonitoringStatus(t){if(!this._hass||0===t.length)return null;const e=this._hass;return function(t){let e=!1;const n={},i={};for(const r of t)r&&(e=!0,r.circuits&&Object.assign(n,r.circuits),r.mains&&Object.assign(i,r.mains));return e?{circuits:n,mains:i}:null}(await Promise.all(t.map(t=>this.monitoringMultiCache.fetchOne(e,t))))}async _buildFavoritesHorizonMaps(){if(!this._hass||!this._favRefs||!this._topology)return;const t=new Set;for(const e of Object.values(this._favRefs))e.configEntryId&&t.add(e.configEntryId);const e=new Map;await Promise.all(Array.from(t).map(async t=>{e.set(t,await this._fetchGraphSettingsFresh(t))})),this.horizonMap.clear(),this.subDeviceHorizonMap.clear();for(const t of Object.keys(this._topology.circuits)){const n=this._favRefs[t],i=n?.configEntryId?e.get(n.configEntryId)??null:null,r=n?.targetId??t;this.horizonMap.set(t,Rk(i,r))}if(this._topology.sub_devices)for(const t of Object.keys(this._topology.sub_devices)){const n=this._favRefs[t],i=n?.configEntryId?e.get(n.configEntryId)??null:null,r=n?.targetId??t;this.subDeviceHorizonMap.set(t,Hk(i,r))}}async loadHistory(){await Me(this._hass,this._topology,this._config,this.powerHistory,this.horizonMap,this.subDeviceHorizonMap)}recordSamples(){if(!this._topology||!this._hass||!this._config)return;const t=Date.now();for(const[e,n]of Object.entries(this._topology.circuits)){const i=this.horizonMap.get(e)??a;if(!s[i]?.useRealtime)continue;const r=Zt(n,this._config);if(!r)continue;const o=this._hass.states[r];if(!o)continue;const l=parseFloat(o.state);if(isNaN(l))continue;const c=me(i),u=ye(c),d=_e(c),h=t-c,p=this.powerHistory.get(e)??[];p.length>0&&t-p[p.length-1].time0&&t-p[p.length-1].time0&&this._topology)for(const{key:t,devId:e}of Ce(this._topology))n.has(e)&&r.add(t);const o=new Map;try{await Me(this._hass,this._topology,this._config,o,e,n);for(const t of e.keys()){const e=o.get(t);e?this.powerHistory.set(t,e):this.powerHistory.delete(t)}for(const t of r){const e=o.get(t);e?this.powerHistory.set(t,e):this.powerHistory.delete(t)}this.updateDOM(t)}catch(t){console.warn("SPAN Panel: history refresh failed",t),this._errorStore?.add({key:"fetch:history",level:"warning",message:i("error.history_failed"),persistent:!1})}}updateDOM(t){this._hass&&this._topology&&this._config&&(function(t,e,n,r,o,a){if(!t||!n||!e)return;const s=ve(r);let l=0;for(const[,t]of Object.entries(n.circuits)){const n=t.entities?.power;if(!n)continue;const i=e.states[n],r=i&&parseFloat(i.state)||0;t.device_type!==u&&(l+=Math.abs(r))}zk(t,e,n,r,l);const d=Yt(r),h="current"===d.entityRole;for(const[r,l]of Object.entries(n.circuits)){const n=t.querySelector(`.circuit-slot[data-uuid="${Ok(r)}"]`);if(!n)continue;const p=l.entities?.power,f=p?e.states[p]:null,g=f&&parseFloat(f.state)||0,v=l.device_type===u||g<0,y=l.entities?.switch,_=y?e.states[y]:null,b=_?"on"===_.state:(f?.attributes?.relay_state||l.relay_state)===c,x=n.querySelector(".power-value");if(x)if(h){const t=l.entities?.current,n=t?e.states[t]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${d.format(i)}A`}else x.innerHTML=`${Ut(g)}${Wt(g)}`;const w=n.querySelector(".toggle-pill");if(w){w.className="toggle-pill "+(b?"toggle-on":"toggle-off");const t=w.querySelector(".toggle-label");t&&(t.textContent=i(b?"grid.on":"grid.off"))}let S;if(n.classList.toggle("circuit-off",!b),n.classList.toggle("circuit-producer",v),l.always_on)S="always_on";else{const t=l.entities?.select,n=t?e.states[t]:null;S=n?n.state:"unknown"}const C=m[S]??m.unknown,M=n.querySelector(".shedding-icon");M&&(M.setAttribute("icon",C.icon),M.style.color=C.color,M.title=C.label());const k=n.querySelector(".shedding-icon-secondary");k&&(C.icon2?(k.setAttribute("icon",C.icon2),k.style.color=C.color,k.style.display=""):k.style.display="none");const T=n.querySelector(".shedding-label");T&&(C.textLabel?(T.textContent=C.textLabel,T.style.color=C.color,T.style.display=""):T.style.display="none");const D=n.querySelector(".chart-container");if(D){const t=o.get(r)||[],e=n.classList.contains("circuit-col-span")?200:100,i=a?.has(r)?me(a.get(r)):s,c=l.device_type===u;Ek(D,0,t,i,d,v,e,l.breaker_rating_a??void 0,c)}}}(t,this._hass,this._topology,this._config,this.powerHistory,this.horizonMap),function(t,e,n,i,r,o){if(!n.sub_devices)return;const a=ve(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=t.querySelector(`[data-subdev="${Ok(i)}"]`);if(!n)continue;const l=ue(s);if(l){const t=e.states[l],i=t&&parseFloat(t.state)||0,r=n.querySelector(".sub-power-value");r&&(r.innerHTML=`${Ut(i)} ${Wt(i)}`)}const c=n.querySelectorAll("[data-chart-key]");for(const t of c){const e=t.dataset.chartKey;if(!e)continue;const n=r.get(e)||[];let s=v.power;e.endsWith("_soc")?s=v.soc:e.endsWith("_soe")&&(s=v.soe);const l=!!t.closest(".bess-chart-col");Ek(t,0,n,o?.has(i)?me(o.get(i)):a,s,!1,l?120:150,void 0,e.endsWith("_soc")||e.endsWith("_soe"))}for(const t of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${Ok(t)}"]`);if(!i)continue;const r=e.states[t];if(r){let t;if(e.formatEntityState)t=e.formatEntityState(r);else{t=r.state;const e=r.attributes.unit_of_measurement||"";e&&(t+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(t=(e/1e3).toFixed(1)+" kWh")}i.textContent=t}}}}(t,this._hass,this._topology,this._config,this.powerHistory,this.subDeviceHorizonMap))}async onGraphSettingsChanged(t){if(this._hass){this._favRefs?await this._buildFavoritesHorizonMaps():(this.graphSettingsCache.invalidate(),await this.graphSettingsCache.fetch(this._hass,this._configEntryId),this.buildHorizonMaps(this.graphSettingsCache.settings)),this.powerHistory.clear();try{await this.loadHistory()}catch{}this.updateDOM(t)}}onToggleClick(t,e){const n=t.target,r=n?.closest(".toggle-pill");if(!r)return;const o=e.querySelector(".slide-confirm");if(!o||!o.classList.contains("confirmed"))return;t.stopPropagation(),t.preventDefault();const a=r.closest("[data-uuid]");if(!a||!this._topology||!this._hass)return;const s=a.dataset.uuid;if(!s)return;const l=this._topology.circuits[s];if(!l)return;const c=l.entities?.switch;if(!c)return;const u=this._hass.states[c];if(!u)return void console.warn("SPAN Panel: switch entity not found:",c);const d="on"===u.state?"turn_off":"turn_on";this._hass.callService("switch",d,{},{entity_id:c}).catch(t=>{console.warn("SPAN Panel: switch service call failed",t),this._errorStore?.add({key:"service:relay",level:"error",message:i("error.relay_failed"),persistent:!1})})}async onGearClick(t,e){const n=t.target,i=n?.closest(".gear-icon");if(!i)return;const r=e.querySelector("span-side-panel");if(!r||!this._hass)return;if(r.hass=this._hass,r.errorStore=this.errorStore,i.classList.contains("panel-gear")){if(this._inFavoritesView){const t=await this._buildFavoritesSections();if(0===t.length)return;return void r.open({favoritesMode:!0,perPanelSections:t})}return await this.graphSettingsCache.fetch(this._hass,this._configEntryId),void r.open({panelMode:!0,topology:this._topology,graphSettings:this.graphSettingsCache.settings,showFavorites:null!==this._panelFavorites,favoritePanelDeviceId:this._panelFavorites?.panelDeviceId,favoriteCircuitUuids:this._panelFavorites?.circuitUuids,favoriteSubDeviceIds:this._panelFavorites?.subDeviceIds,configEntryId:this._configEntryId})}const o=i.dataset.uuid;if(o&&this._topology){const t=this._topology.circuits[o];if(t){const e=this._favRefs?.[o]??null,n=e&&"circuit"===e.kind?e.targetId:o,i=e?.configEntryId??this._configEntryId;let s,l;e?[s,l]=await Promise.all([this._fetchGraphSettingsFresh(i),this._fetchMonitoringStatusFresh(i)]):(await Promise.all([this.graphSettingsCache.fetch(this._hass,i),this.monitoringCache.fetch(this._hass,i)]),s=this.graphSettingsCache.settings,l=this.monitoringCache.status);const c=t.entities?.current??t.entities?.power,u=c?l?.circuits?.[c]??null:null,d=s?.global_horizon??a,h=s?.circuits?.[n],p=h?{...h,globalHorizon:d}:{horizon:d,has_override:!1,globalHorizon:d},f=e?.panelDeviceId??this._panelFavorites?.panelDeviceId,g=null!==e||(this._panelFavorites?.circuitUuids.has(n)??!1),v=this._inFavoritesView||null!==this._panelFavorites;return void r.open({...t,uuid:n,monitoringInfo:u,showMonitoring:this._showMonitoring,graphHorizonInfo:p,showFavorites:v,favoritePanelDeviceId:f,isFavorite:g,configEntryId:i})}}const s=i.dataset.subdevId;if(s&&this._topology?.sub_devices?.[s]){const t=this._topology.sub_devices[s],e=this._favRefs?.[s]??null,n=e&&"sub_device"===e.kind?e.targetId:s,i=e?.configEntryId??this._configEntryId;let o;e?o=await this._fetchGraphSettingsFresh(i):(await this.graphSettingsCache.fetch(this._hass,i),o=this.graphSettingsCache.settings);const l=o?.global_horizon??a,c=o?.sub_devices?.[n],u=c?{...c,globalHorizon:l}:{horizon:l,has_override:!1,globalHorizon:l},d=e?.panelDeviceId??this._panelFavorites?.panelDeviceId,h=null!==e||(this._panelFavorites?.subDeviceIds.has(n)??!1),p=this._inFavoritesView||null!==this._panelFavorites;r.open({subDeviceMode:!0,subDeviceId:n,name:t.name??n,deviceType:t.type??"",entities:t.entities,graphHorizonInfo:u,showFavorites:p,favoritePanelDeviceId:d,isFavorite:h,configEntryId:i})}}async _buildFavoritesSections(){if(!this._hass||!this._favRefs)return[];const t=function(t,e){const n=new Map;for(const i of Object.values(t)){if("circuit"!==i.kind)continue;const t=e.get(i.panelDeviceId);if(void 0===t)continue;let r=n.get(i.panelDeviceId);void 0===r&&(r={panelDeviceId:i.panelDeviceId,panelName:t.panelName,topology:t.topology,configEntryId:t.configEntryId,favoriteCircuitUuids:new Set},n.set(i.panelDeviceId,r)),r.favoriteCircuitUuids.add(i.targetId)}return Array.from(n.values()).sort((t,e)=>t.panelName.localeCompare(e.panelName))}(this._favRefs,this._perPanelInfo);if(0===t.length)return[];return await Promise.all(t.map(async t=>({panelDeviceId:t.panelDeviceId,panelName:t.panelName,topology:t.topology,graphSettings:await this._fetchGraphSettingsFresh(t.configEntryId),favoriteCircuitUuids:t.favoriteCircuitUuids,configEntryId:t.configEntryId})))}async _fetchGraphSettingsFresh(t){if(!this._hass)return null;try{const e={};t&&(e.config_entry_id=t);const n={type:"call_service",domain:l,service:"get_graph_settings",service_data:e,return_response:!0},r=this._errorStore?new Qt(this._errorStore):null,o=r?await r.callWS(this._hass,n,{errorId:"fetch:graph_settings",errorMessage:i("error.graph_settings_failed")}):await this._hass.callWS(n);return o?.response??null}catch(t){return console.warn("SPAN Panel: fresh graph settings fetch failed",t),null}}async _fetchMonitoringStatusFresh(t){if(!this._hass)return null;try{const e={};t&&(e.config_entry_id=t);const n={type:"call_service",domain:l,service:"get_monitoring_status",service_data:e,return_response:!0},r=this._errorStore?new Qt(this._errorStore):null,o=r?await r.callWS(this._hass,n,{errorId:"fetch:monitoring",errorMessage:i("error.monitoring_failed")}):await this._hass.callWS(n),a=o?.response;return a?{circuits:a.circuits,mains:a.mains}:null}catch(t){return console.warn("SPAN Panel: fresh monitoring status fetch failed",t),null}}bindSlideConfirm(t,e){const n=t.querySelector(".slide-confirm-knob"),i=t.querySelector(".slide-confirm-text");if(!n||!i)return;let r=!1,o=0,a=0;const s=e=>{t.classList.contains("confirmed")||(r=!0,o=e-n.offsetLeft,a=t.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},l=t=>{if(!r)return;const e=Math.max(2,Math.min(t-o,a));n.style.left=e+"px"},c=()=>{if(!r)return;r=!1;(n.offsetLeft-2)/a>=.9?(n.style.left=a+"px",t.classList.add("confirmed"),n.querySelector("span-icon")?.setAttribute("icon","mdi:lock-open"),i.textContent=t.dataset.textOn??"",e&&e.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",t=>{t.preventDefault(),s(t.clientX)}),t.addEventListener("mousemove",t=>l(t.clientX)),t.addEventListener("mouseup",c),t.addEventListener("mouseleave",c),n.addEventListener("touchstart",t=>{t.preventDefault(),s(t.touches[0].clientX)},{passive:!1}),t.addEventListener("touchmove",t=>l(t.touches[0].clientX),{passive:!0}),t.addEventListener("touchend",c),t.addEventListener("touchcancel",c),t.addEventListener("click",()=>{t.classList.contains("confirmed")&&(t.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("span-icon")?.setAttribute("icon","mdi:lock"),i.textContent=t.dataset.textOff??"",e&&e.classList.add("switches-disabled"))})}startIntervals(t,e){this._updateInterval=setInterval(()=>{this.recordSamples(),this.updateDOM(t),e&&e()},1e3),this._recorderRefreshInterval=setInterval(()=>{this.refreshRecorderData(t)},3e4)}stopIntervals(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null),this.cleanupResizeObserver()}setupResizeObserver(t,e){this.cleanupResizeObserver(),e&&(this._lastWidth=e.clientWidth,this._resizeObserver=new ResizeObserver(e=>{const n=e[0];if(!n)return;const i=n.contentRect.width;Math.abs(i-this._lastWidth)<5||(this._lastWidth=i,this._resizeDebounce&&clearTimeout(this._resizeDebounce),this._resizeDebounce=setTimeout(()=>{for(const e of t.querySelectorAll(".chart-container")){const t=e.querySelector("span-chart");t&&t.remove()}this.updateDOM(t)},150))}),this._resizeObserver.observe(e))}cleanupResizeObserver(){this._resizeObserver&&(this._resizeObserver.disconnect(),this._resizeObserver=null),this._resizeDebounce&&(clearTimeout(this._resizeDebounce),this._resizeDebounce=null)}reset(){this.powerHistory.clear(),this.horizonMap.clear(),this.subDeviceHorizonMap.clear(),this.monitoringCache.clear(),this.monitoringMultiCache.clear(),this.graphSettingsCache.clear()}}function Fk(t=""){const e=t?` value="${Rt(t)}"`:"",n=t?"":"display:none;";return`\n
\n \n \n
\n `}function $k(t,e,n,r,o,a,s){const l=e.entities?.power,u=l?n.states[l]:null,d=u&&parseFloat(u.state)||0,h=e.entities?.switch,p=h?n.states[h]:null,f=p?"on"===p.state:(u?.attributes?.relay_state||e.relay_state)===c,g=e.breaker_rating_a,v=g?`${Math.round(g)}A`:"",y=Rt(e.name||i("grid.unknown")),_=Yt(r),b="current"===_.entityRole;let x;if(f)if(b){const t=e.entities?.current,i=t?n.states[t]:null,r=i&&parseFloat(i.state)||0;x=`${_.format(r)}A`}else x=`${Ut(d)}${Wt(d)}`;else x="";const w=a||"unknown";let S="";if("unknown"!==w){const t=m[w]??m.unknown??{icon:"mdi:help",color:"#999",label:()=>"Unknown"};S=t.icon2?`\n \n \n `:t.textLabel?`\n \n ${t.textLabel}\n `:``}let C="",M=o?.utilization_pct??null;if(null==M&&e.breaker_rating_a){const t=e.entities?.current,i=t?n.states[t]:null,r=i?Math.abs(parseFloat(i.state)||0):0;M=Math.round(r/e.breaker_rating_a*1e3)/10}if(null!=M){C=`=80?"utilization-warning":"utilization-normal"}">${Math.round(M)}%`}const k=``,T=!1!==e.is_user_controllable&&!!e.entities?.switch?`
\n ${i(f?"grid.on":"grid.off")}\n \n
`:`${f?"ON":"OFF"}`;return`\n
\n ${v?`${v}`:""}\n ${C}\n ${y}\n ${S}\n ${T}\n \n ${x}\n \n ${k}\n \n
\n `}function Vk(t,e,n,i,r){const o=e.entities?.power,a=o?n.states[o]:null,s=a&&parseFloat(a.state)||0,l=e.device_type===u||s<0,d=e.entities?.switch,h=d?n.states[d]:null,p=ne(0,r,h?"on"===h.state:(a?.attributes?.relay_state||e.relay_state)===c,l),f=Rt(t);return`\n
\n
\n
\n
\n
\n `}function Wk(t){return`
${Rt(t)}
`}function Uk(t,e){const n=e.hysteresisPx??48,i=new WeakSet;let r=null;const o=t=>{const n=t.querySelector(e.nameSelector);return!!n&&(0!==t.clientWidth&&(n.clientWidth<1||n.scrollWidth>n.clientWidth+1))},a=()=>{const i=t.querySelectorAll(e.rowSelector);if(0===i.length)return;const a=i[0].clientWidth;if(0===a)return;if(null!==r){if(a>r+n){for(const t of i)t.classList.remove(e.foldClass);r=null}else for(const t of i)t.classList.add(e.foldClass);return}let s=!1;for(const t of i)if(o(t)){s=!0;break}if(s){for(const t of i)t.classList.add(e.foldClass);r=a}},s=new ResizeObserver(()=>a()),l=()=>{const n=t.querySelectorAll(e.rowSelector);for(const t of n)i.has(t)||(i.add(t),s.observe(t));requestAnimationFrame(()=>a())};l();const c=new MutationObserver(t=>{(t=>{const n=t=>{for(const n of t)if(n instanceof Element){if(n.matches(e.rowSelector))return!0;if(n.querySelector(e.rowSelector))return!0}return!1};for(const e of t)if(n(e.addedNodes)||n(e.removedNodes))return!0;return!1})(t)&&l()});return c.observe(t,{childList:!0,subtree:!0}),()=>{s.disconnect(),c.disconnect()}}function Gk(t,e,n){const i=t.entities?.switch,r=i?e.states[i]:null,o=t.entities?.power,a=o?e.states[o]:null,s=r?"on"===r.state:(a?.attributes?.relay_state||t.relay_state)===c;let l;if("current"===(n.chart_metric||"power")){const n=t.entities?.current,i=n?e.states[n]:null;l=i?Math.abs(parseFloat(i.state)||0):0}else l=a?Math.abs(parseFloat(a.state)||0):0;return{isOn:s,value:l}}function qk(t,e){if(t.always_on)return"always_on";const n=t.entities?.select,i=n?e.states[n]:null;return i?i.state:"unknown"}function jk(t,e,n,i){const r=Gk(t,n,i),o=Gk(e,n,i);return r.isOn&&!o.isOn?-1:!r.isOn&&o.isOn?1:o.value-r.value}function Xk(t,e,n){return t.sort((t,i)=>jk(t[1],i[1],e,n))}function Yk(t){return t.entities?.current??t.entities?.power??""}class Zk{constructor(t){this._expandedUuids=new Set,this._searchQuery="",this._container=null,this._clickHandler=null,this._inputHandler=null,this._graphSettingsHandler=null,this._hass=null,this._topology=null,this._config=null,this._monitoringStatus=null,this._viewName=null,this._columns=1,this._foldUnobserve=null,this._ctrl=t}setColumns(t){const e=Math.max(1,Math.min(3,Math.floor(t)));this._columns=e}setInitialExpansion(t){this._expandedUuids=new Set(t)}setInitialSearchQuery(t){this._searchQuery=t}setViewName(t){this._viewName=t}renderActivityView(t,e,n,i,r,o){this._unbindEvents(),this._hass=e,this._topology=n,this._config=i,this._monitoringStatus=r;const a=Xk(Object.entries(n.circuits),e,i);let s=o+Fk(this._searchQuery);s+=`
`;for(const[t,n]of a){const o=ee(r,Yk(n)),a=qk(n,e),l=this._expandedUuids.has(t);s+=`
`,s+=$k(t,n,e,i,o,a,l),l&&(s+=Vk(t,n,e,0,o)),s+="
"}s+="
",s+="",t.innerHTML=s;const l=t.querySelector("span-side-panel");l&&(l.hass=e,l.errorStore=this._ctrl.errorStore),this._bindEvents(t),this._searchQuery&&this._applyFilter(t),this._ctrl.updateDOM(t),this._attachFoldObserver(t)}renderAreaView(t,e,n,r,o,a){this._unbindEvents(),this._hass=e,this._topology=n,this._config=r,this._monitoringStatus=o;const s=i("list.unassigned_area"),l=new Map;for(const[t,e]of Object.entries(n.circuits)){const n=e.area??s,i=l.get(n);i?i.push([t,e]):l.set(n,[[t,e]])}const c=[...l.keys()].sort((t,e)=>t===s?1:e===s?-1:t.localeCompare(e));let u=a+Fk(this._searchQuery);u+=`
`;for(const t of c){const n=l.get(t);if(!n)continue;const i=Xk(n,e,r);u+=Wk(t);for(const[t,n]of i){const i=ee(o,Yk(n)),a=qk(n,e),s=this._expandedUuids.has(t);u+=`
`,u+=$k(t,n,e,r,i,a,s),s&&(u+=Vk(t,n,e,0,i)),u+="
"}}u+="
",u+="",t.innerHTML=u;const d=t.querySelector("span-side-panel");d&&(d.hass=e,d.errorStore=this._ctrl.errorStore),this._bindEvents(t),this._searchQuery&&this._applyFilter(t),this._ctrl.updateDOM(t),this._attachFoldObserver(t)}updateCollapsedRows(t,e,n,r){const o=Yt(r),a="current"===o.entityRole,s=t.querySelectorAll(".list-row[data-row-uuid]");for(const t of s){const s=t.dataset.rowUuid;if(!s)continue;const l=n.circuits[s];if(!l)continue;const{isOn:c,value:u}=Gk(l,e,r),d=t.querySelector(".list-power-value");if(d)if(c)if(a)d.innerHTML=`${o.format(u)}A`;else{const t=l.entities?.power,n=t?e.states[t]:null,i=n&&parseFloat(n.state)||0;d.innerHTML=`${Ut(i)}${Wt(i)}`}else d.innerHTML="";const h=t.querySelector(".toggle-pill");if(h){h.classList.toggle("toggle-on",c),h.classList.toggle("toggle-off",!c);const t=h.querySelector(".toggle-label");t&&(t.textContent=i(c?"grid.on":"grid.off"))}const p=t.querySelector(".list-status-badge");p&&(p.textContent=c?"ON":"OFF",p.classList.toggle("list-status-on",c),p.classList.toggle("list-status-off",!c)),t.classList.toggle("circuit-off",!c)}!function(t,e,n,i){const r=t.querySelector(".list-view");if(r)for(const t of function(t,e){let n={anchor:null,units:[]};const i=[n];for(const r of[...t.children])if(r.classList.contains("area-header"))n={anchor:r,units:[]},i.push(n);else if(r.classList.contains("list-cell")){const t=r.dataset.cellUuid,i=t?e.circuits[t]:void 0;t&&i&&n.units.push({cell:r,uuid:t,circuit:i})}return i}(r,n)){if(t.units.length<2)continue;const n=[...t.units].sort((t,n)=>jk(t.circuit,n.circuit,e,i));if(!n.some((e,n)=>e.uuid!==t.units[n].uuid))continue;let o=t.anchor;for(const t of n)o?o.after(t.cell):r.prepend(t.cell),o=t.cell}}(t,e,n,r)}stop(){this._unbindEvents(),null===this._viewName&&(this._expandedUuids.clear(),this._searchQuery=""),this._hass=null,this._topology=null,this._config=null,this._monitoringStatus=null}_dispatchFavoritesViewState(){if(!this._viewName||!this._container)return;const t={view:this._viewName,expanded:[...this._expandedUuids],searchQuery:this._searchQuery};this._container.dispatchEvent(new CustomEvent("favorites-view-state-changed",{detail:t,bubbles:!0,composed:!0}))}_bindEvents(t){this._container=t,this._clickHandler=e=>{const n=e.target;if(!n)return;const i=n.closest(".list-expand-toggle");if(i){const t=i.dataset.expandUuid;return void(t&&this._toggleExpand(t))}if(n.closest(".gear-icon"))return void this._ctrl.onGearClick(e,t);if(n.closest(".toggle-pill"))return void this._ctrl.onToggleClick(e,t);if(n.closest(".list-search-clear")){const e=t.querySelector(".list-search");return void(e&&(e.value="",e.dispatchEvent(new Event("input",{bubbles:!0}))))}const r=n.closest(".unit-btn");if(r){const e=r.dataset.unit;e&&t.dispatchEvent(new CustomEvent("unit-changed",{detail:e,bubbles:!0,composed:!0}))}},this._inputHandler=e=>{const n=e.target;n&&n.classList.contains("list-search")&&(this._searchQuery=n.value.toLowerCase(),this._applyFilter(t),this._dispatchFavoritesViewState())},this._graphSettingsHandler=()=>{this._ctrl.onGraphSettingsChanged(t).then(()=>{this._ctrl.updateDOM(t)}).catch(()=>{})},t.addEventListener("click",this._clickHandler),t.addEventListener("input",this._inputHandler),t.addEventListener("graph-settings-changed",this._graphSettingsHandler);const e=t.querySelector(".slide-confirm");e&&(this._ctrl.bindSlideConfirm(e,t),t.classList.add("switches-disabled"))}_unbindEvents(){this._container&&(this._clickHandler&&this._container.removeEventListener("click",this._clickHandler),this._inputHandler&&this._container.removeEventListener("input",this._inputHandler),this._graphSettingsHandler&&this._container.removeEventListener("graph-settings-changed",this._graphSettingsHandler)),this._foldUnobserve&&(this._foldUnobserve(),this._foldUnobserve=null),this._container=null,this._clickHandler=null,this._inputHandler=null,this._graphSettingsHandler=null}_attachFoldObserver(t){this._foldUnobserve&&(this._foldUnobserve(),this._foldUnobserve=null),this._foldUnobserve=Uk(t,{rowSelector:".list-row",nameSelector:".list-circuit-name",foldClass:"is-folded"})}_applyFilter(t){const e=t.querySelector(".list-search-clear");e&&(e.style.display=this._searchQuery?"":"none");const n=t.querySelectorAll(".list-cell[data-cell-uuid]");for(const t of n){const e=t.querySelector(".list-circuit-name"),n=(e?.textContent?.toLowerCase()??"").includes(this._searchQuery);t.style.display=n?"":"none"}const i=t.querySelectorAll(".area-header");for(const t of i){let e=!1,n=t.nextElementSibling;for(;n&&!n.classList.contains("area-header");){if(n.classList.contains("list-cell")&&"none"!==n.style.display){e=!0;break}n=n.nextElementSibling}t.style.display=e?"":"none"}}_toggleExpand(t){if(!(this._container&&this._hass&&this._topology&&this._config))return;const e=Ok(t),n=this._container.querySelector(`.list-cell[data-cell-uuid="${e}"]`);if(!n)return;const i=n.querySelector(`.list-row[data-row-uuid="${e}"]`),r=n.querySelector(`.list-expand-toggle[data-expand-uuid="${e}"]`);if(i){if(this._expandedUuids.has(t)){this._expandedUuids.delete(t);const o=n.querySelector(`.list-expanded-content[data-expanded-uuid="${e}"]`);o&&o.remove(),r&&r.classList.remove("expanded"),i.classList.remove("list-row-expanded")}else{this._expandedUuids.add(t);const e=this._topology.circuits[t];if(!e)return;const n=ee(this._monitoringStatus,Yk(e)),o=Vk(t,e,this._hass,this._config,n);i.insertAdjacentHTML("afterend",o),r&&r.classList.add("expanded"),i.classList.add("list-row-expanded"),this._ctrl.updateDOM(this._container)}this._dispatchFavoritesViewState()}}}async function Kk(t,e){const[n,i,r]=await Promise.all([t.callWS({type:"config/area_registry/list"}),t.callWS({type:"config/entity_registry/list"}),t.callWS({type:"config/device_registry/list"})]),o=new Map;for(const t of n)o.set(t.area_id,t.name);const a=new Map;for(const t of i)t.area_id&&a.set(t.entity_id,t.area_id);const s=new Map;for(const t of r)s.set(t.id,t.area_id);let l;if(e.device_id){const t=s.get(e.device_id);t&&(l=o.get(t))}for(const t of Object.values(e.circuits)){let e;for(const n of Object.values(t.entities)){if(!n)continue;const t=a.get(n);if(t){e=o.get(t);break}}e||(e=l),t.area=e}}class Qk{constructor(){this._persistent=new Map,this._transient=null,this._transientTimer=null,this._subscribers=new Set,this._watchedPanels=new Map}add(t){const e={...t,timestamp:Date.now()};if(e.persistent)this._persistent.set(e.key,e);else{this._clearTransient(),this._transient=e;const t=e.ttl??5e3;this._transientTimer=setTimeout(()=>{this._transient=null,this._transientTimer=null,this._notify()},t)}this._notify()}remove(t){if(this._persistent.has(t))return this._persistent.delete(t),void this._notify();this._transient?.key===t&&(this._clearTransient(),this._notify())}clear(t){void 0===t?(this._persistent.clear(),this._clearTransient(),this._watchedPanels.clear()):!0===t.persistent?this._persistent.clear():!1===t.persistent&&this._clearTransient(),this._notify()}get active(){const t=[...this._persistent.values()];return null!==this._transient&&t.push(this._transient),t}hasPersistent(t){return this._persistent.has(t)}hasAnyPanelOffline(){for(const t of this._persistent.keys())if("panel-offline"===t||t.startsWith("panel-offline:"))return!0;return!1}subscribe(t){return this._subscribers.add(t),()=>{this._subscribers.delete(t)}}watchPanelStatus(t){this.watchPanelStatuses([{entityId:t,panelName:null}])}watchPanelStatuses(t){const e=this._watchedPanels,n=new Map;for(const i of t){const t=e.get(i.entityId);n.set(i.entityId,{panelName:i.panelName??null,wasOffline:t?.wasOffline??!1})}const i=this._isSingleUnnamed(e),r=this._isSingleUnnamed(n);for(const t of e.keys()){n.has(t)&&i===r||this._persistent.delete(this._offlineKey(t,i))}this._watchedPanels=n,this._notify()}clearPanelStatusWatch(){if(0===this._watchedPanels.size)return;const t=this._isSingleUnnamed(this._watchedPanels);for(const e of this._watchedPanels.keys())this._persistent.delete(this._offlineKey(e,t));this._watchedPanels.clear(),this._notify()}updateHass(t){if(0===this._watchedPanels.size)return;const e=this._isSingleUnnamed(this._watchedPanels);for(const[n,o]of this._watchedPanels){const a=t.states[n]?.state,s="on"===a,l=this._offlineKey(n,e),c=this._reconnectKey(n,e);if(s){const t=o.wasOffline;o.wasOffline=!1,this.remove(l),t&&this.add({key:c,level:"info",message:null===o.panelName?i("error.panel_reconnected"):r("error.panel_reconnected_named",{name:o.panelName}),persistent:!1})}else o.wasOffline=!0,this.hasPersistent(l)||this.add({key:l,level:"error",message:null===o.panelName?i("error.panel_offline"):r("error.panel_offline_named",{name:o.panelName}),persistent:!0})}}dispose(){this._clearTransient(),this._persistent.clear(),this._subscribers.clear(),this._watchedPanels.clear()}_isSingleUnnamed(t){if(1!==t.size)return!1;for(const e of t.values())return null===e.panelName;return!1}_offlineKey(t,e){return e?"panel-offline":`panel-offline:${t}`}_reconnectKey(t,e){return e?"panel-reconnected":`panel-reconnected:${t}`}_clearTransient(){null!==this._transientTimer&&(clearTimeout(this._transientTimer),this._transientTimer=null),this._transient=null}_notify(){for(const t of this._subscribers)try{t()}catch(t){console.warn("SPAN Panel: error-store subscriber threw",t)}}}function Jk(t){let e=0;for(const n of Object.values(t))if(n)for(const t of n.tabs)t>e&&(e=t);return e>0?e+e%2:0}function tT(t){return t?{id:t.id,name:t.name,name_by_user:t.name_by_user,config_entries:t.config_entries,identifiers:t.identifiers,via_device_id:t.via_device_id,sw_version:t.sw_version,model:t.model}:null}const eT="favorites-changed";async function nT(t,e,n={}){const i=await t.callWS({type:"call_service",domain:l,service:e,service_data:n,return_response:!0});return i?.response??null}const iT=Object.keys(m).filter(t=>"unknown"!==t&&"always_on"!==t);class rT extends HTMLElement{constructor(){super(),this.errorStore=null,this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(t){this._hass=t,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}disconnectedCallback(){this._clearDebounceTimers(),this._config=null}open(t){this._config=t,this._render(),this.offsetHeight,this.setAttribute("open",""),this.setAttribute("data-mode",this._modeFor(t))}close(){this._clearDebounceTimers(),this.removeAttribute("open"),this.removeAttribute("data-mode"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_clearDebounceTimers(){for(const t of Object.keys(this._debounceTimers))clearTimeout(this._debounceTimers[t]);this._debounceTimers={}}_modeFor(t){return t.favoritesMode?"favorites":t.panelMode?"panel":t.subDeviceMode?"subDevice":"circuit"}_render(){const t=this._config;if(!t)return;const e=this.shadowRoot;if(!e)return;e.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .unit-toggle {\n display: inline-flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n }\n .unit-btn {\n padding: 4px 10px;\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.15s ease, color 0.15s ease;\n }\n .unit-btn:last-child {\n border-right: none;\n }\n .unit-btn:hover:not(.unit-active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .unit-btn.unit-active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .fav-heart {\n background: none;\n border: 1px solid var(--divider-color, #e0e0e0);\n color: var(--secondary-text-color, #727272);\n border-radius: 4px;\n padding: 2px 6px;\n cursor: pointer;\n font-size: 0.9em;\n margin-right: 6px;\n line-height: 1;\n display: inline-flex;\n align-items: center;\n }\n .fav-heart.active {\n color: var(--primary-color, #03a9f4);\n border-color: var(--primary-color, #03a9f4);\n }\n .fav-heart:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .fav-heart span-icon {\n --mdc-icon-size: 16px;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n',e.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),e.appendChild(i);const r=document.createElement("div");r.className="panel",e.appendChild(r),t.favoritesMode?this._renderFavoritesMode(r):t.panelMode?this._renderPanelMode(r):t.subDeviceMode?this._renderSubDeviceMode(r,t):this._renderCircuitMode(r,t)}_renderPanelMode(t){const e=this._config,n=this._createHeader(i("sidepanel.graph_settings"),i("sidepanel.global_defaults"));t.appendChild(n);const r=document.createElement("div");r.className="panel-body";const o=e.graphSettings,l=e.topology,c=o?.global_horizon??a,u=o?.circuits??{};r.appendChild(this._buildListColumnsSection());const d=document.createElement("div");d.className="section";const h=document.createElement("div");h.className="section-label",h.textContent=i("sidepanel.graph_horizon"),d.appendChild(h);const p=document.createElement("div");p.className="field-row";const g=document.createElement("span");g.className="field-label",g.textContent=i("sidepanel.global_default"),p.appendChild(g);const v=document.createElement("select");for(const t of Object.keys(s)){const e=document.createElement("option");e.value=t;const n=`horizon.${t}`,r=i(n);e.textContent=r!==n?r:t,t===c&&(e.selected=!0),v.appendChild(e)}if(v.addEventListener("change",()=>{const t={horizon:v.value};e.configEntryId&&(t.config_entry_id=e.configEntryId),this._callDomainService("set_graph_time_horizon",t).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(t=>{console.warn("SPAN Panel: graph horizon service failed",t),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:i("error.graph_horizon_failed"),persistent:!1})})}),p.appendChild(v),d.appendChild(p),r.appendChild(d),l?.circuits){const t=document.createElement("div");t.className="section";const n=document.createElement("div");n.className="section-label",n.textContent=i("sidepanel.circuit_scales"),t.appendChild(n);const o=Object.entries(l.circuits).sort(([,t],[,e])=>(t.name||"").localeCompare(e.name||""));for(const[n,i]of o){const r=this._buildPanelModeCircuitRow(n,i,u[n],c,e.configEntryId??null,e.showFavorites??!1,e.favoritePanelDeviceId,e.favoriteCircuitUuids);t.appendChild(r)}r.appendChild(t)}const m=o?.sub_devices??{};if(l?.sub_devices){const t=document.createElement("div");t.className="section";const n=document.createElement("div");n.className="section-label",n.textContent=i("sidepanel.subdevice_scales"),t.appendChild(n);const o=Object.entries(l.sub_devices).sort(([,t],[,e])=>(t.name||"").localeCompare(e.name||""));for(const[n,r]of o){const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");if(a.className="field-label",a.textContent=r.name||n,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",o.appendChild(a),e.showFavorites&&e.favoritePanelDeviceId){const t=this._buildSubDeviceFavoriteHeart(r.entities,e.favoriteSubDeviceIds?.has(n)??!1);t&&o.appendChild(t)}const l=m[n]||{horizon:c,has_override:!1},u=l.has_override?l.horizon:c,d=document.createElement("select");d.dataset.subdevId=n;for(const t of Object.keys(s)){const e=document.createElement("option");e.value=t;const n=`horizon.${t}`,r=i(n);e.textContent=r!==n?r:t,t===u&&(e.selected=!0),d.appendChild(e)}if(d.addEventListener("change",()=>{this._debounce(`subdev-${n}`,f,()=>{const t={subdevice_id:n,horizon:d.value};e.configEntryId&&(t.config_entry_id=e.configEntryId),this._callDomainService("set_subdevice_graph_horizon",t).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(t=>{console.warn("SPAN Panel: graph horizon service failed",t),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:i("error.graph_horizon_failed"),persistent:!1})})})}),o.appendChild(d),l.has_override){const t=document.createElement("button");t.textContent="↺",t.title=i("sidepanel.reset_to_global"),Object.assign(t.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),t.addEventListener("click",()=>{const r={subdevice_id:n};e.configEntryId&&(r.config_entry_id=e.configEntryId),this._callDomainService("clear_subdevice_graph_horizon",r).then(()=>{d.value=c,t.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(t=>{console.warn("SPAN Panel: graph horizon service failed",t),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:i("error.graph_horizon_failed"),persistent:!1})})}),o.appendChild(t)}t.appendChild(o)}r.appendChild(t)}t.appendChild(r)}_buildPanelModeCircuitRow(t,e,n,r,o,a,l,c){const u=document.createElement("div");u.className="field-row";const d=document.createElement("span");if(d.className="field-label",d.textContent=e.name||t,d.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",u.appendChild(d),a&&l){const n=this._buildFavoriteHeart(e.entities,c?.has(t)??!1);n&&u.appendChild(n)}const h=n||{horizon:r,has_override:!1},p=h.has_override?h.horizon:r,g=document.createElement("select");g.dataset.uuid=t;for(const t of Object.keys(s)){const e=document.createElement("option");e.value=t;const n=`horizon.${t}`,r=i(n);e.textContent=r!==n?r:t,t===p&&(e.selected=!0),g.appendChild(e)}if(g.addEventListener("change",()=>{this._debounce(`circuit-${t}`,f,()=>{const e={circuit_id:t,horizon:g.value};o&&(e.config_entry_id=o),this._callDomainService("set_circuit_graph_horizon",e).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(t=>{console.warn("SPAN Panel: graph horizon service failed",t),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:i("error.graph_horizon_failed"),persistent:!1})})})}),u.appendChild(g),h.has_override){const e=document.createElement("button");e.textContent="↺",e.title=i("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{const n={circuit_id:t};o&&(n.config_entry_id=o),this._callDomainService("clear_circuit_graph_horizon",n).then(()=>{g.value=r,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(t=>{console.warn("SPAN Panel: graph horizon service failed",t),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:i("error.graph_horizon_failed"),persistent:!1})})}),u.appendChild(e)}return u}_renderFavoritesMode(t){const e=this._config,n=this._createHeader(i("sidepanel.graph_settings"),i("sidepanel.favorites_subtitle"));t.appendChild(n);const r=document.createElement("div");r.className="panel-body",r.appendChild(this._buildListColumnsSection());for(const t of e.perPanelSections)r.appendChild(this._buildFavoritesPanelSection(t));t.appendChild(r)}_buildFavoritesPanelSection(t){const e=document.createElement("div");e.className="section";const n=document.createElement("div");n.className="section-label",n.textContent=t.panelName,e.appendChild(n);const i=t.graphSettings?.global_horizon??a,r=t.graphSettings?.circuits??{},o=function(t){const e=t.circuits??{};return Object.entries(e).map(([t,e])=>({uuid:t,circuit:e})).sort((t,e)=>(t.circuit.name||"").localeCompare(e.circuit.name||""))}(t.topology);for(const{uuid:n,circuit:a}of o){const o=this._buildPanelModeCircuitRow(n,a,r[n],i,t.configEntryId,!0,t.panelDeviceId,t.favoriteCircuitUuids);e.appendChild(o)}return e}_renderCircuitMode(t,e){const n=`${Rt(String(e.breaker_rating_a))}A · ${Rt(String(e.voltage))}V · Tabs [${Rt(String(e.tabs))}]`,i=this._createHeader(Rt(e.name),n);t.appendChild(i);const r=document.createElement("div");r.className="panel-body",t.appendChild(r),this._renderRelaySection(r,e),e.showFavorites&&this._renderFavoriteSection(r,e),this._renderSheddingSection(r,e),this._renderGraphHorizonSection(r,e),e.showMonitoring&&this._renderMonitoringSection(r,e)}_favoriteEntityId(t){return t?.current??t?.power??null}_subDeviceFavoriteEntityId(t){if(!t)return null;let e=null;for(const[n,i]of Object.entries(t)){if("sensor"===i.domain)return n;e||(e=n)}return e}_buildSubDeviceFavoriteHeart(t,e){const n=this._subDeviceFavoriteEntityId(t);return n?this._buildHeartButton(n,e):null}_buildListColumnsSection(){const t=document.createElement("div");t.className="section";const e=document.createElement("div");e.className="section-label",e.textContent=i("sidepanel.list_view_columns"),t.appendChild(e);const n=document.createElement("div");n.className="field-row";const r=document.createElement("span");r.className="field-label",r.textContent=i("sidepanel.columns"),n.appendChild(r);const o=Bt(),a=document.createElement("div");a.className="unit-toggle";for(const t of[1,2,3]){const e=document.createElement("button");e.type="button",e.className="unit-btn"+(t===o?" unit-active":""),e.dataset.columns=String(t),e.textContent=String(t),e.addEventListener("click",()=>{Ft(t);for(const t of a.querySelectorAll(".unit-btn"))t.classList.toggle("unit-active",t===e);this.dispatchEvent(new CustomEvent("list-columns-changed",{detail:t,bubbles:!0,composed:!0}))}),a.appendChild(e)}return n.appendChild(a),t.appendChild(n),t}_buildFavoriteHeart(t,e){const n=this._favoriteEntityId(t);return n?this._buildHeartButton(n,e):(console.warn("SPAN Panel: circuit has no current/power sensor; favorite heart suppressed"),null)}_buildHeartButton(t,e){const n=document.createElement("button");n.type="button",n.className=e?"fav-heart active":"fav-heart",n.dataset.role="fav-heart",n.title=i("sidepanel.save_to_favorites"),n.setAttribute("role","switch"),n.setAttribute("aria-checked",String(e)),n.setAttribute("aria-label",i("sidepanel.save_to_favorites"));const r=document.createElement("span-icon");return r.setAttribute("icon",e?"mdi:heart":"mdi:heart-outline"),n.appendChild(r),n.addEventListener("click",e=>{e.stopPropagation(),this._toggleFavoriteEntity(n,r,t).catch(()=>{})}),n}async _toggleFavoriteEntity(t,e,n){if(!this._hass)return;const r=t.classList.contains("active"),o=!r;t.classList.toggle("active",o),e.setAttribute("icon",o?"mdi:heart":"mdi:heart-outline"),t.setAttribute("aria-checked",String(o));try{o?await async function(t,e){const n=await nT(t,"add_favorite",{entity_id:e});return document.dispatchEvent(new CustomEvent(eT)),n?.favorites??{}}(this._hass,n):await async function(t,e){const n=await nT(t,"remove_favorite",{entity_id:e});return document.dispatchEvent(new CustomEvent(eT)),n?.favorites??{}}(this._hass,n)}catch(n){throw t.classList.toggle("active",r),e.setAttribute("icon",r?"mdi:heart":"mdi:heart-outline"),t.setAttribute("aria-checked",String(r)),console.warn("SPAN Panel: favorite toggle failed",n),this.errorStore?.add({key:"service:favorites",level:"error",message:i("error.favorites_toggle_failed"),persistent:!1}),n}}_renderFavoriteSection(t,e){const n=this._favoriteEntityId(e.entities);n&&this._appendFavoriteHeartSection(t,n,!0===e.isFavorite)}_appendFavoriteHeartSection(t,e,n){const r=document.createElement("div");r.className="section",r.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=i("sidepanel.save_to_favorites"),o.appendChild(a),o.appendChild(this._buildHeartButton(e,n)),r.appendChild(o),t.appendChild(r)}_renderSubDeviceMode(t,e){const n=this._createHeader(Rt(e.name),Rt(e.deviceType));t.appendChild(n);const i=document.createElement("div");i.className="panel-body",t.appendChild(i),e.showFavorites&&this._renderSubDeviceFavoriteSection(i,e),this._renderSubDeviceHorizonSection(i,e)}_renderSubDeviceFavoriteSection(t,e){const n=this._subDeviceFavoriteEntityId(e.entities);n&&this._appendFavoriteHeartSection(t,n,!0===e.isFavorite)}_renderSubDeviceHorizonSection(t,e){const n=document.createElement("div");n.className="section";const r=document.createElement("div");r.className="section-label",r.textContent=i("sidepanel.graph_horizon"),n.appendChild(r);const o=e.graphHorizonInfo,l=!0===o?.has_override,c=o?.horizon||a,u=o?.globalHorizon||a,d=document.createElement("div");d.className="horizon-bar";const h=[{key:"global",label:i("sidepanel.global")}];for(const t of Object.keys(s))h.push({key:t,label:t});const p=l?c:"global",f=t=>{for(const e of d.querySelectorAll(".horizon-segment")){const n=e.dataset.horizon;e.classList.toggle("active",n===t),e.classList.toggle("referenced","global"===t&&n===u)}};for(const{key:t,label:n}of h){const r=document.createElement("button");r.type="button",r.className="horizon-segment",r.dataset.horizon=t,r.textContent=n,r.classList.toggle("active",t===p),r.classList.toggle("referenced","global"===p&&t===u),r.addEventListener("click",()=>{if(r.classList.contains("active"))return;const n={subdevice_id:e.subDeviceId};e.configEntryId&&(n.config_entry_id=e.configEntryId),"global"===t?(f("global"),this._callDomainService("clear_subdevice_graph_horizon",n).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(t=>{console.warn("SPAN Panel: graph horizon service failed",t),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:i("error.graph_horizon_failed"),persistent:!1})})):(f(t),this._callDomainService("set_subdevice_graph_horizon",{...n,horizon:t}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(t=>{console.warn("SPAN Panel: graph horizon service failed",t),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:i("error.graph_horizon_failed"),persistent:!1})}))}),d.appendChild(r)}n.appendChild(d),t.appendChild(n)}_createHeader(t,e){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div"),r=Rt(t),o=Rt(e);i.innerHTML=`
${r}
`+(o?`
${o}
`:"");const a=document.createElement("button");return a.className="close-btn",a.innerHTML="✕",a.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(a),n}_renderRelaySection(t,e){if(!1===e.is_user_controllable||!e.entities?.switch)return;const n=document.createElement("div");n.className="section",n.innerHTML=``;const r=document.createElement("div");r.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent=i("sidepanel.breaker");const a=document.createElement("span-switch");a.dataset.role="relay-toggle";const s=e.entities.switch,l=this._hass?.states?.[s]?.state;"on"===l&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const t=a.hasAttribute("checked")||a.checked;this._callService("switch",t?"turn_on":"turn_off",{entity_id:s}).catch(t=>{console.warn("SPAN Panel: relay toggle failed",t),this.errorStore?.add({key:"service:relay",level:"error",message:i("error.relay_failed"),persistent:!1})})}),r.appendChild(o),r.appendChild(a),n.appendChild(r),t.appendChild(n)}_renderSheddingSection(t,e){if(!e.entities?.select)return;const n=document.createElement("div");n.className="section",n.innerHTML=``;const r=document.createElement("div");r.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent=i("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const s=e.entities.select,l=this._hass?.states?.[s]?.state||"";for(const t of iT){const e=m[t];if(!e)continue;const n=document.createElement("option");n.value=t,n.textContent=i(`shedding.select.${t}`)||e.label(),t===l&&(n.selected=!0),a.appendChild(n)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:s,option:a.value}).catch(t=>{console.warn("SPAN Panel: shedding update failed",t),this.errorStore?.add({key:"service:shedding",level:"error",message:i("error.shedding_failed"),persistent:!1})})}),r.appendChild(o),r.appendChild(a),n.appendChild(r),t.appendChild(n)}_renderGraphHorizonSection(t,e){const n=document.createElement("div");n.className="section";const r=document.createElement("div");r.className="section-label",r.textContent=i("sidepanel.graph_horizon"),n.appendChild(r);const o=e.graphHorizonInfo,l=!0===o?.has_override,c=o?.horizon||a,u=o?.globalHorizon||a,d=document.createElement("div");d.className="horizon-bar";const h=[{key:"global",label:i("sidepanel.global")}];for(const t of Object.keys(s))h.push({key:t,label:t});const p=l?c:"global",f=t=>{for(const e of d.querySelectorAll(".horizon-segment")){const n=e.dataset.horizon;e.classList.toggle("active",n===t),e.classList.toggle("referenced","global"===t&&n===u)}};for(const{key:t,label:n}of h){const r=document.createElement("button");r.type="button",r.className="horizon-segment",r.dataset.horizon=t,r.textContent=n,r.classList.toggle("active",t===p),r.classList.toggle("referenced","global"===p&&t===u),r.addEventListener("click",()=>{if(r.classList.contains("active"))return;const n={circuit_id:e.uuid};e.configEntryId&&(n.config_entry_id=e.configEntryId),"global"===t?(f("global"),this._callDomainService("clear_circuit_graph_horizon",n).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(t=>{console.warn("SPAN Panel: graph horizon service failed",t),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:i("error.graph_horizon_failed"),persistent:!1})})):(f(t),this._callDomainService("set_circuit_graph_horizon",{...n,horizon:t}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(t=>{console.warn("SPAN Panel: graph horizon service failed",t),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:i("error.graph_horizon_failed"),persistent:!1})}))}),d.appendChild(r)}n.appendChild(d),t.appendChild(n)}_renderMonitoringSection(t,e){const n=document.createElement("div");n.className="section";const r=document.createElement("div");r.className="monitoring-header";const o=document.createElement("div");o.className="section-label",o.textContent=i("sidepanel.monitoring"),o.style.margin="0";const a=document.createElement("span-switch");a.dataset.role="monitoring-toggle";const s=e.monitoringInfo,l=null!=s&&!1!==s.monitoring_enabled;l&&a.setAttribute("checked",""),r.appendChild(o),r.appendChild(a),n.appendChild(r);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",n.appendChild(c);const u=!0===s?.has_override,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,c.appendChild(d);const h=document.createElement("div");h.dataset.role="threshold-fields",h.style.display=u?"block":"none";const p=s?.continuous_threshold_pct??80,f=s?.spike_threshold_pct??100,g=s?.window_duration_m??15,v=s?.cooldown_duration_m??15;h.appendChild(this._createThresholdRow(i("sidepanel.continuous_pct"),"continuous",p,e)),h.appendChild(this._createThresholdRow(i("sidepanel.spike_pct"),"spike",f,e)),h.appendChild(this._createDurationRow(i("sidepanel.window_duration"),"window-m",g,1,180,"m",e)),h.appendChild(this._createDurationRow(i("sidepanel.cooldown"),"cooldown-m",v,1,180,"m",e)),c.appendChild(h),a.addEventListener("change",()=>{const t=a.checked;c.style.display=t?"block":"none";const n={circuit_id:e.entities?.power||e.uuid,monitoring_enabled:t};e.configEntryId&&(n.config_entry_id=e.configEntryId),this._callDomainService("set_circuit_threshold",n).catch(t=>{console.warn("SPAN Panel: monitoring update failed",t),this.errorStore?.add({key:"service:monitoring",level:"error",message:i("error.threshold_failed"),persistent:!1})})});const m=d.querySelectorAll('input[type="radio"]');for(const t of m)t.addEventListener("change",()=>{const n="custom"===t.value&&t.checked;if(h.style.display=n?"block":"none",!n&&t.checked){const t={circuit_id:e.entities?.power||e.uuid};e.configEntryId&&(t.config_entry_id=e.configEntryId),this._callDomainService("clear_circuit_threshold",t).catch(t=>{console.warn("SPAN Panel: monitoring update failed",t),this.errorStore?.add({key:"service:monitoring",level:"error",message:i("error.threshold_failed"),persistent:!1})})}});t.appendChild(n)}_createThresholdRow(t,e,n,r){const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=t;const s=document.createElement("input");return s.type="number",s.min="0",s.max="200",s.value=String(n),s.dataset.role=`threshold-${e}`,s.addEventListener("input",()=>{this._debounce(`threshold-${e}`,f,()=>{const t=this.shadowRoot;if(!t)return;const e=t.querySelector('[data-role="threshold-continuous"]'),n=t.querySelector('[data-role="threshold-spike"]'),o=t.querySelector('[data-role="threshold-window-m"]'),a=t.querySelector('[data-role="threshold-cooldown-m"]'),s={circuit_id:r.entities?.power||r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:o?Number(o.value):void 0,cooldown_duration_m:a?Number(a.value):void 0};r.configEntryId&&(s.config_entry_id=r.configEntryId),this._callDomainService("set_circuit_threshold",s).catch(t=>{console.warn("SPAN Panel: monitoring update failed",t),this.errorStore?.add({key:"service:monitoring",level:"error",message:i("error.threshold_failed"),persistent:!1})})})}),o.appendChild(a),o.appendChild(s),o}_createDurationRow(t,e,n,r,o,a,s,l=!1){const c=document.createElement("div");c.className="field-row";const u=document.createElement("span");u.className="field-label",u.textContent=t;const d=document.createElement("div"),h=document.createElement("input");h.type="number",h.min=String(r),h.max=String(o),h.value=String(n),h.dataset.role=`threshold-${e}`,l&&(h.disabled=!0);const p=document.createElement("span");return p.textContent=a,d.appendChild(h),d.appendChild(p),l||h.addEventListener("input",()=>{this._debounce(`threshold-${e}`,f,()=>{const t=this.shadowRoot;if(!t)return;const e=t.querySelector('[data-role="threshold-continuous"]'),n=t.querySelector('[data-role="threshold-spike"]'),r=t.querySelector('[data-role="threshold-window-m"]'),o={circuit_id:s.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:r?Number(r.value):void 0};s.configEntryId&&(o.config_entry_id=s.configEntryId),this._callDomainService("set_circuit_threshold",o).catch(t=>{console.warn("SPAN Panel: monitoring update failed",t),this.errorStore?.add({key:"service:monitoring",level:"error",message:i("error.threshold_failed"),persistent:!1})})})}),c.appendChild(u),c.appendChild(d),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const t=this._config;if(!t.subDeviceMode&&!t.favoritesMode){if(t.entities?.switch){const e=this.shadowRoot?.querySelector('[data-role="relay-toggle"]');if(e){const n=this._hass?.states?.[t.entities.switch]?.state;"on"===n?e.setAttribute("checked",""):e.removeAttribute("checked")}}if(t.entities?.select){const e=this.shadowRoot?.querySelector('[data-role="shedding-select"]');if(e){const n=this._hass?.states?.[t.entities.select]?.state||"";e.value=n}}}}_callService(t,e,n){return this._hass?Promise.resolve(this._hass.callService(t,e,n)):Promise.resolve()}_callDomainService(t,e){return this._hass?this._hass.callWS({type:"call_service",domain:l,service:t,service_data:e}):Promise.resolve()}_debounce(t,e,n){this._debounceTimers[t]&&clearTimeout(this._debounceTimers[t]),this._debounceTimers[t]=setTimeout(()=>{delete this._debounceTimers[t],n()},e)}}try{customElements.get("span-side-panel")||customElements.define("span-side-panel",rT)}catch{}class oT extends It{constructor(){super(...arguments),this._store=null,this._unsub=null,this._errors=[]}set store(t){if(this._store===t)return;this._unsub?.(),this._unsub=null,this._store=t,this._errors=t.active;const e=t;this._unsub=t.subscribe(()=>{this._errors=e.active})}connectedCallback(){if(super.connectedCallback(),this._store&&!this._unsub){const t=this._store;this._errors=t.active,this._unsub=t.subscribe(()=>{this._errors=t.active})}}disconnectedCallback(){super.disconnectedCallback(),this._unsub?.(),this._unsub=null}render(){return 0===this._errors.length?ft:dt`${this._errors.map(t=>dt` + `,b([Ot({attribute:!1})],Lk.prototype,"options",void 0),b([Ot({attribute:!1})],Lk.prototype,"data",void 0),b([Ot({type:String})],Lk.prototype,"height",void 0);try{customElements.get("span-chart")||customElements.define("span-chart",Lk)}catch{}function Ek(t,e,n,i,r,a,s,l,c){const{options:u,series:d}=function(t,e,n,i,r,a=!1){n||(n=g[o]);const s=i?"140, 160, 220":"77, 217, 175",l=`rgb(${s})`,c=Date.now(),u=c-e,d=void 0!==n.fixedMin&&void 0!==n.fixedMax,h=(t??[]).filter(t=>t.time>=u).map(t=>[t.time,Math.abs(t.value)]),p=[{type:"line",data:h,showSymbol:!1,smooth:!1,...a?{}:{step:"end"},lineStyle:{width:1.5,color:l},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${s}, 0.18)`},{offset:1,color:`rgba(${s}, 0.18)`}]}},itemStyle:{color:l}}],f=h.length>0?function(t){let e=0;for(const n of t)n[1]>e&&(e=n[1]);return e}(h):0,v={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:f<10?t=>0===t?"0":t.toFixed(1):t=>n.format(t)},splitLine:{lineStyle:{opacity:.15}}};d?(v.min=n.fixedMin,v.max=n.fixedMax):f<1&&(v.min=0,v.max=1),r&&"current"===n.entityRole&&(v.min=0,v.max=Math.ceil(1.25*r),p.push({type:"line",data:[[u,.8*r],[c,.8*r]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),p.push({type:"line",data:[[u,r],[c,r]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}}));const m={xAxis:{type:"time",min:u,max:c,axisLabel:{fontSize:10},splitLine:{show:!1}},yAxis:v,grid:{top:8,right:4,bottom:0,left:0,containLabel:!0},tooltip:{trigger:"axis",axisPointer:{type:"line",lineStyle:{type:"dashed"}},formatter:t=>{if(!t||0===t.length)return"";const e=t[0],i=new Date(e.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"}),r=parseFloat(e.value[1].toFixed(2));return`
${i}
${n.format(r)} ${n.unit(r)}
`}},animation:!1};return{options:m,series:p}}(n,i,r,a,l,c),h=s??120;t.style.minHeight=h+"px";let p=t.querySelector("span-chart");p||(p=document.createElement("span-chart"),p.style.display="block",p.style.width="100%",t.innerHTML="",t.appendChild(p));const f=t.clientHeight;p.height=(f>0?f:h)+"px",p.options=u,p.data=d}function Ok(t){return"function"==typeof globalThis.CSS?.escape?CSS.escape(t):t.replace(/["\\]/g,"\\$&")}function zk(t,e,n,i,r){const o=t.querySelector(".panel-stats");o&&function(t,e,n,i,r){const o="current"===(i.chart_metric||"power"),a=t.querySelector(".stat-consumption .stat-value"),s=t.querySelector(".stat-consumption .stat-unit");if(o){const t=n.panel_entities?.site_power,i=t?e.states[t]:null,r=i?parseFloat(i.attributes?.amperage):NaN;a&&(a.textContent=Number.isFinite(r)?Math.abs(r).toFixed(1):"--"),s&&(s.textContent="A")}else{let t=r;const i=n.panel_entities?.site_power;if(i){const n=e.states[i];n&&(t=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=Gt(t)),s&&(s.textContent="kW")}const l=t.querySelector(".stat-upstream .stat-value"),c=t.querySelector(".stat-upstream .stat-unit");if(l){const t=n.panel_entities?.current_power,i=t?e.states[t]:null;if(o){const t=i?parseFloat(i.attributes?.amperage):NaN;l.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",c&&(c.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;l.textContent=Gt(t),c&&(c.textContent="kW")}}const u=t.querySelector(".stat-downstream .stat-value"),d=t.querySelector(".stat-downstream .stat-unit");if(u){const t=n.panel_entities?.feedthrough_power,i=t?e.states[t]:null;if(o){const t=i?parseFloat(i.attributes?.amperage):NaN;u.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",d&&(d.textContent="A")}else{const t=i?Math.abs(parseFloat(i.state)||0):0;u.textContent=Gt(t),d&&(d.textContent="kW")}}const h=t.querySelector(".stat-solar .stat-value"),p=t.querySelector(".stat-solar .stat-unit");if(h){const t=n.panel_entities?.pv_power,i=t?e.states[t]:null;if(o){const t=i?parseFloat(i.attributes?.amperage):NaN;h.textContent=Number.isFinite(t)?Math.abs(t).toFixed(1):"--",p&&(p.textContent="A")}else{if(i){const t=Math.abs(parseFloat(i.state)||0);h.textContent=Gt(t)}else h.textContent="--";p&&(p.textContent="kW")}}const f=t.querySelector(".stat-battery .stat-value");if(f){const t=n.panel_entities?.battery_level,i=t?e.states[t]:null;i&&(f.textContent=`${Math.round(parseFloat(i.state)||0)}`)}const g=t.querySelector(".stat-grid-state .stat-value");if(g){const t=n.panel_entities?.dsm_state,i=t?e.states[t]:null;g.textContent=i?e.formatEntityState?.(i)||i.state:"--"}}(o,e,n,i,r)}class Nk{get errorStore(){return this._errorStore}set errorStore(t){this._errorStore=t,this._retry=t?new Qt(t):null}constructor(){this._errorStore=null,this._retry=null,this._settings=null,this._lastFetch=0,this._fetching=!1}async fetch(t,e){const n=Date.now();if(this._fetching)return this._settings;if(this._settings&&n-this._lastFetch<3e4)return this._settings;this._fetching=!0;try{const n={};e&&(n.config_entry_id=e);const r={type:"call_service",domain:l,service:"get_graph_settings",service_data:n,return_response:!0},o=this._retry?await this._retry.callWS(t,r,{errorId:"fetch:graph_settings",errorMessage:i("error.graph_settings_failed")}):await t.callWS(r);this._settings=o?.response??null,this._lastFetch=Date.now()}catch(t){console.warn("SPAN Panel: graph settings fetch failed",t),this._settings=null,this._retry||this._errorStore?.add({key:"fetch:graph_settings",level:"warning",message:i("error.graph_settings_failed"),persistent:!1})}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}function Rk(t,e){if(!t)return a;const n=t.circuits?.[e];return n?.has_override?n.horizon:t.global_horizon??a}function Hk(t,e){if(!t)return a;const n=t.sub_devices?.[e];return n?.has_override?n.horizon:t.global_horizon??a}class Bk{constructor(){this.powerHistory=new Map,this.horizonMap=new Map,this.subDeviceHorizonMap=new Map,this.monitoringCache=new Jt,this.monitoringMultiCache=new te,this.graphSettingsCache=new Nk,this._errorStore=null,this._hass=null,this._topology=null,this._config=null,this._configEntryId=null,this._favRefs=null,this._perPanelInfo=new Map,this._panelFavorites=null,this._showMonitoring=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._resizeObserver=null,this._lastWidth=0,this._resizeDebounce=null}get errorStore(){return this._errorStore}set errorStore(t){this._errorStore=t,this.monitoringCache.errorStore=t,this.graphSettingsCache.errorStore=t,this.monitoringMultiCache.errorStore=t}get hass(){return this._hass}set hass(t){this._hass=t}get topology(){return this._topology}get config(){return this._config}set showMonitoring(t){this._showMonitoring=t}init(t,e,n,i){this._topology=t,this._config=e,this._hass=n,this._configEntryId=i}setFavoriteRefs(t){this._favRefs=t}clearFavoriteRefs(){this._favRefs=null}setPanelFavorites(t){this._panelFavorites=t}setFavoritesPerPanelInfo(t){this._perPanelInfo=t??new Map}get _inFavoritesView(){return null!==this._favRefs}setConfig(t){this._config=t}buildHorizonMaps(t){if(this.horizonMap.clear(),this.subDeviceHorizonMap.clear(),t&&this._topology?.circuits)for(const e of Object.keys(this._topology.circuits))this.horizonMap.set(e,Rk(t,e));if(t&&this._topology?.sub_devices)for(const e of Object.keys(this._topology.sub_devices))this.subDeviceHorizonMap.set(e,Hk(t,e))}async fetchAndBuildHorizonMaps(){try{this._favRefs?await this._buildFavoritesHorizonMaps():(await this.graphSettingsCache.fetch(this._hass,this._configEntryId),this.buildHorizonMaps(this.graphSettingsCache.settings))}catch(t){console.warn("SPAN Panel: graph settings fetch failed",t),this.graphSettingsCache.errorStore||this._errorStore?.add({key:"fetch:graph_settings",level:"warning",message:i("error.graph_settings_failed"),persistent:!1})}}async fetchMergedMonitoringStatus(t){if(!this._hass||0===t.length)return null;const e=this._hass;return function(t){let e=!1;const n={},i={};for(const r of t)r&&(e=!0,r.circuits&&Object.assign(n,r.circuits),r.mains&&Object.assign(i,r.mains));return e?{circuits:n,mains:i}:null}(await Promise.all(t.map(t=>this.monitoringMultiCache.fetchOne(e,t))))}async _buildFavoritesHorizonMaps(){if(!this._hass||!this._favRefs||!this._topology)return;const t=new Set;for(const e of Object.values(this._favRefs))e.configEntryId&&t.add(e.configEntryId);const e=new Map;await Promise.all(Array.from(t).map(async t=>{e.set(t,await this._fetchGraphSettingsFresh(t))})),this.horizonMap.clear(),this.subDeviceHorizonMap.clear();for(const t of Object.keys(this._topology.circuits)){const n=this._favRefs[t],i=n?.configEntryId?e.get(n.configEntryId)??null:null,r=n?.targetId??t;this.horizonMap.set(t,Rk(i,r))}if(this._topology.sub_devices)for(const t of Object.keys(this._topology.sub_devices)){const n=this._favRefs[t],i=n?.configEntryId?e.get(n.configEntryId)??null:null,r=n?.targetId??t;this.subDeviceHorizonMap.set(t,Hk(i,r))}}async loadHistory(){await Me(this._hass,this._topology,this._config,this.powerHistory,this.horizonMap,this.subDeviceHorizonMap)}recordSamples(){if(!this._topology||!this._hass||!this._config)return;const t=Date.now();for(const[e,n]of Object.entries(this._topology.circuits)){const i=this.horizonMap.get(e)??a;if(!s[i]?.useRealtime)continue;const r=Zt(n,this._config);if(!r)continue;const o=this._hass.states[r];if(!o)continue;const l=parseFloat(o.state);if(isNaN(l))continue;const c=me(i),u=ye(c),d=_e(c),h=t-c,p=this.powerHistory.get(e)??[];p.length>0&&t-p[p.length-1].time0&&t-p[p.length-1].time0&&this._topology)for(const{key:t,devId:e}of Ce(this._topology))n.has(e)&&r.add(t);const o=new Map;try{await Me(this._hass,this._topology,this._config,o,e,n);for(const t of e.keys()){const e=o.get(t);e?this.powerHistory.set(t,e):this.powerHistory.delete(t)}for(const t of r){const e=o.get(t);e?this.powerHistory.set(t,e):this.powerHistory.delete(t)}this.updateDOM(t)}catch(t){console.warn("SPAN Panel: history refresh failed",t),this._errorStore?.add({key:"fetch:history",level:"warning",message:i("error.history_failed"),persistent:!1})}}updateDOM(t){this._hass&&this._topology&&this._config&&(function(t,e,n,r,o,a){if(!t||!n||!e)return;const s=ve(r);let l=0;for(const[,t]of Object.entries(n.circuits)){const n=t.entities?.power;if(!n)continue;const i=e.states[n],r=i&&parseFloat(i.state)||0;t.device_type!==u&&(l+=Math.abs(r))}zk(t,e,n,r,l);const d=Yt(r),h="current"===d.entityRole;for(const[r,l]of Object.entries(n.circuits)){const n=t.querySelector(`.circuit-slot[data-uuid="${Ok(r)}"]`);if(!n)continue;const p=l.entities?.power,f=p?e.states[p]:null,g=f&&parseFloat(f.state)||0,v=l.device_type===u||g<0,y=l.entities?.switch,_=y?e.states[y]:null,b=_?"on"===_.state:(f?.attributes?.relay_state||l.relay_state)===c,x=n.querySelector(".power-value");if(x)if(h){const t=l.entities?.current,n=t?e.states[t]:null,i=n&&parseFloat(n.state)||0;x.innerHTML=`${d.format(i)}A`}else x.innerHTML=`${Ut(g)}${Wt(g)}`;const w=n.querySelector(".toggle-pill");if(w){w.className="toggle-pill "+(b?"toggle-on":"toggle-off");const t=w.querySelector(".toggle-label");t&&(t.textContent=i(b?"grid.on":"grid.off"))}let S;if(n.classList.toggle("circuit-off",!b),n.classList.toggle("circuit-producer",v),l.always_on)S="always_on";else{const t=l.entities?.select,n=t?e.states[t]:null;S=n?n.state:"unknown"}const C=m[S]??m.unknown,M=n.querySelector(".shedding-icon");M&&(M.setAttribute("icon",C.icon),M.style.color=C.color,M.title=C.label());const k=n.querySelector(".shedding-icon-secondary");k&&(C.icon2?(k.setAttribute("icon",C.icon2),k.style.color=C.color,k.style.display=""):k.style.display="none");const T=n.querySelector(".shedding-label");T&&(C.textLabel?(T.textContent=C.textLabel,T.style.color=C.color,T.style.display=""):T.style.display="none");const D=n.querySelector(".chart-container");if(D){const t=o.get(r)||[],e=n.classList.contains("circuit-col-span")?200:100,i=a?.has(r)?me(a.get(r)):s,c=l.device_type===u;Ek(D,0,t,i,d,v,e,l.breaker_rating_a??void 0,c)}}}(t,this._hass,this._topology,this._config,this.powerHistory,this.horizonMap),function(t,e,n,i,r,o){if(!n.sub_devices)return;const a=ve(i);for(const[i,s]of Object.entries(n.sub_devices)){const n=t.querySelector(`[data-subdev="${Ok(i)}"]`);if(!n)continue;const l=ue(s);if(l){const t=e.states[l],i=t&&parseFloat(t.state)||0,r=n.querySelector(".sub-power-value");r&&(r.innerHTML=`${Ut(i)} ${Wt(i)}`)}const c=n.querySelectorAll("[data-chart-key]");for(const t of c){const e=t.dataset.chartKey;if(!e)continue;const n=r.get(e)||[];let s=v.power;e.endsWith("_soc")?s=v.soc:e.endsWith("_soe")&&(s=v.soe);const l=!!t.closest(".bess-chart-col");Ek(t,0,n,o?.has(i)?me(o.get(i)):a,s,!1,l?120:150,void 0,e.endsWith("_soc")||e.endsWith("_soe"))}for(const t of Object.keys(s.entities||{})){const i=n.querySelector(`[data-eid="${Ok(t)}"]`);if(!i)continue;const r=e.states[t];if(r){let t;if(e.formatEntityState)t=e.formatEntityState(r);else{t=r.state;const e=r.attributes.unit_of_measurement||"";e&&(t+=" "+e)}if("Wh"===(r.attributes.unit_of_measurement||"")){const e=parseFloat(r.state);isNaN(e)||(t=(e/1e3).toFixed(1)+" kWh")}i.textContent=t}}}}(t,this._hass,this._topology,this._config,this.powerHistory,this.subDeviceHorizonMap))}async onGraphSettingsChanged(t){if(this._hass){this._favRefs?await this._buildFavoritesHorizonMaps():(this.graphSettingsCache.invalidate(),await this.graphSettingsCache.fetch(this._hass,this._configEntryId),this.buildHorizonMaps(this.graphSettingsCache.settings)),this.powerHistory.clear();try{await this.loadHistory()}catch{}this.updateDOM(t)}}onToggleClick(t,e){const n=t.target,r=n?.closest(".toggle-pill");if(!r)return;const o=e.querySelector(".slide-confirm");if(!o||!o.classList.contains("confirmed"))return;t.stopPropagation(),t.preventDefault();const a=r.closest("[data-uuid]");if(!a||!this._topology||!this._hass)return;const s=a.dataset.uuid;if(!s)return;const l=this._topology.circuits[s];if(!l)return;const c=l.entities?.switch;if(!c)return;const u=this._hass.states[c];if(!u)return void console.warn("SPAN Panel: switch entity not found:",c);const d="on"===u.state?"turn_off":"turn_on";this._hass.callService("switch",d,{},{entity_id:c}).catch(t=>{console.warn("SPAN Panel: switch service call failed",t),this._errorStore?.add({key:"service:relay",level:"error",message:i("error.relay_failed"),persistent:!1})})}async onGearClick(t,e){const n=t.target,i=n?.closest(".gear-icon");if(!i)return;const r=e.querySelector("span-side-panel");if(!r||!this._hass)return;if(r.hass=this._hass,r.errorStore=this.errorStore,i.classList.contains("panel-gear")){if(this._inFavoritesView){const t=await this._buildFavoritesSections();if(0===t.length)return;return void r.open({favoritesMode:!0,perPanelSections:t})}return await this.graphSettingsCache.fetch(this._hass,this._configEntryId),void r.open({panelMode:!0,topology:this._topology,graphSettings:this.graphSettingsCache.settings,showFavorites:null!==this._panelFavorites,favoritePanelDeviceId:this._panelFavorites?.panelDeviceId,favoriteCircuitUuids:this._panelFavorites?.circuitUuids,favoriteSubDeviceIds:this._panelFavorites?.subDeviceIds,configEntryId:this._configEntryId})}const o=i.dataset.uuid;if(o&&this._topology){const t=this._topology.circuits[o];if(t){const e=this._favRefs?.[o]??null,n=e&&"circuit"===e.kind?e.targetId:o,i=e?.configEntryId??this._configEntryId;let s,l;e?[s,l]=await Promise.all([this._fetchGraphSettingsFresh(i),this._fetchMonitoringStatusFresh(i)]):(await Promise.all([this.graphSettingsCache.fetch(this._hass,i),this.monitoringCache.fetch(this._hass,i)]),s=this.graphSettingsCache.settings,l=this.monitoringCache.status);const c=t.entities?.current??t.entities?.power,u=c?l?.circuits?.[c]??null:null,d=s?.global_horizon??a,h=s?.circuits?.[n],p=h?{...h,globalHorizon:d}:{horizon:d,has_override:!1,globalHorizon:d},f=e?.panelDeviceId??this._panelFavorites?.panelDeviceId,g=null!==e||(this._panelFavorites?.circuitUuids.has(n)??!1),v=this._inFavoritesView||null!==this._panelFavorites;return void r.open({...t,uuid:n,monitoringInfo:u,showMonitoring:this._showMonitoring,graphHorizonInfo:p,showFavorites:v,favoritePanelDeviceId:f,isFavorite:g,configEntryId:i})}}const s=i.dataset.subdevId;if(s&&this._topology?.sub_devices?.[s]){const t=this._topology.sub_devices[s],e=this._favRefs?.[s]??null,n=e&&"sub_device"===e.kind?e.targetId:s,i=e?.configEntryId??this._configEntryId;let o;e?o=await this._fetchGraphSettingsFresh(i):(await this.graphSettingsCache.fetch(this._hass,i),o=this.graphSettingsCache.settings);const l=o?.global_horizon??a,c=o?.sub_devices?.[n],u=c?{...c,globalHorizon:l}:{horizon:l,has_override:!1,globalHorizon:l},d=e?.panelDeviceId??this._panelFavorites?.panelDeviceId,h=null!==e||(this._panelFavorites?.subDeviceIds.has(n)??!1),p=this._inFavoritesView||null!==this._panelFavorites;r.open({subDeviceMode:!0,subDeviceId:n,name:t.name??n,deviceType:t.type??"",entities:t.entities,graphHorizonInfo:u,showFavorites:p,favoritePanelDeviceId:d,isFavorite:h,configEntryId:i})}}async _buildFavoritesSections(){if(!this._hass||!this._favRefs)return[];const t=function(t,e){const n=new Map;for(const i of Object.values(t)){if("circuit"!==i.kind)continue;const t=e.get(i.panelDeviceId);if(void 0===t)continue;let r=n.get(i.panelDeviceId);void 0===r&&(r={panelDeviceId:i.panelDeviceId,panelName:t.panelName,topology:t.topology,configEntryId:t.configEntryId,favoriteCircuitUuids:new Set},n.set(i.panelDeviceId,r)),r.favoriteCircuitUuids.add(i.targetId)}return Array.from(n.values()).sort((t,e)=>t.panelName.localeCompare(e.panelName))}(this._favRefs,this._perPanelInfo);if(0===t.length)return[];return await Promise.all(t.map(async t=>({panelDeviceId:t.panelDeviceId,panelName:t.panelName,topology:t.topology,graphSettings:await this._fetchGraphSettingsFresh(t.configEntryId),favoriteCircuitUuids:t.favoriteCircuitUuids,configEntryId:t.configEntryId})))}async _fetchGraphSettingsFresh(t){if(!this._hass)return null;try{const e={};t&&(e.config_entry_id=t);const n={type:"call_service",domain:l,service:"get_graph_settings",service_data:e,return_response:!0},r=this._errorStore?new Qt(this._errorStore):null,o=r?await r.callWS(this._hass,n,{errorId:"fetch:graph_settings",errorMessage:i("error.graph_settings_failed")}):await this._hass.callWS(n);return o?.response??null}catch(t){return console.warn("SPAN Panel: fresh graph settings fetch failed",t),null}}async _fetchMonitoringStatusFresh(t){if(!this._hass)return null;try{const e={};t&&(e.config_entry_id=t);const n={type:"call_service",domain:l,service:"get_monitoring_status",service_data:e,return_response:!0},r=this._errorStore?new Qt(this._errorStore):null,o=r?await r.callWS(this._hass,n,{errorId:"fetch:monitoring",errorMessage:i("error.monitoring_failed")}):await this._hass.callWS(n),a=o?.response;return a?{circuits:a.circuits,mains:a.mains}:null}catch(t){return console.warn("SPAN Panel: fresh monitoring status fetch failed",t),null}}bindSlideConfirm(t,e){const n=t.querySelector(".slide-confirm-knob"),i=t.querySelector(".slide-confirm-text");if(!n||!i)return;let r=!1,o=0,a=0;const s=e=>{t.classList.contains("confirmed")||(r=!0,o=e-n.offsetLeft,a=t.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},l=t=>{if(!r)return;const e=Math.max(2,Math.min(t-o,a));n.style.left=e+"px"},c=()=>{if(!r)return;r=!1;(n.offsetLeft-2)/a>=.9?(n.style.left=a+"px",t.classList.add("confirmed"),n.querySelector("span-icon")?.setAttribute("icon","mdi:lock-open"),i.textContent=t.dataset.textOn??"",e&&e.classList.remove("switches-disabled")):(n.classList.add("snapping"),n.style.left="2px")};n.addEventListener("mousedown",t=>{t.preventDefault(),s(t.clientX)}),t.addEventListener("mousemove",t=>l(t.clientX)),t.addEventListener("mouseup",c),t.addEventListener("mouseleave",c),n.addEventListener("touchstart",t=>{t.preventDefault(),s(t.touches[0].clientX)},{passive:!1}),t.addEventListener("touchmove",t=>l(t.touches[0].clientX),{passive:!0}),t.addEventListener("touchend",c),t.addEventListener("touchcancel",c),t.addEventListener("click",()=>{t.classList.contains("confirmed")&&(t.classList.remove("confirmed"),n.classList.add("snapping"),n.style.left="2px",n.querySelector("span-icon")?.setAttribute("icon","mdi:lock"),i.textContent=t.dataset.textOff??"",e&&e.classList.add("switches-disabled"))})}startIntervals(t,e){this._updateInterval=setInterval(()=>{this.recordSamples(),this.updateDOM(t),e&&e()},1e3),this._recorderRefreshInterval=setInterval(()=>{this.refreshRecorderData(t)},3e4)}stopIntervals(){this._updateInterval&&(clearInterval(this._updateInterval),this._updateInterval=null),this._recorderRefreshInterval&&(clearInterval(this._recorderRefreshInterval),this._recorderRefreshInterval=null),this.cleanupResizeObserver()}setupResizeObserver(t,e){this.cleanupResizeObserver(),e&&(this._lastWidth=e.clientWidth,this._resizeObserver=new ResizeObserver(e=>{const n=e[0];if(!n)return;const i=n.contentRect.width;Math.abs(i-this._lastWidth)<5||(this._lastWidth=i,this._resizeDebounce&&clearTimeout(this._resizeDebounce),this._resizeDebounce=setTimeout(()=>{for(const e of t.querySelectorAll(".chart-container")){const t=e.querySelector("span-chart");t&&t.remove()}this.updateDOM(t)},150))}),this._resizeObserver.observe(e))}cleanupResizeObserver(){this._resizeObserver&&(this._resizeObserver.disconnect(),this._resizeObserver=null),this._resizeDebounce&&(clearTimeout(this._resizeDebounce),this._resizeDebounce=null)}reset(){this.powerHistory.clear(),this.horizonMap.clear(),this.subDeviceHorizonMap.clear(),this.monitoringCache.clear(),this.monitoringMultiCache.clear(),this.graphSettingsCache.clear()}}function Fk(t=""){const e=t?` value="${Rt(t)}"`:"",n=t?"":"display:none;";return`\n
\n \n \n
\n `}function $k(t,e,n,r,o,a,s){const l=e.entities?.power,u=l?n.states[l]:null,d=u&&parseFloat(u.state)||0,h=e.entities?.switch,p=h?n.states[h]:null,f=p?"on"===p.state:(u?.attributes?.relay_state||e.relay_state)===c,g=e.breaker_rating_a,v=g?`${Math.round(g)}A`:"",y=Rt(e.name||i("grid.unknown")),_=Yt(r),b="current"===_.entityRole;let x;if(f)if(b){const t=e.entities?.current,i=t?n.states[t]:null,r=i&&parseFloat(i.state)||0;x=`${_.format(r)}A`}else x=`${Ut(d)}${Wt(d)}`;else x="";const w=a||"unknown";let S="";if("unknown"!==w){const t=m[w]??m.unknown??{icon:"mdi:help",color:"#999",label:()=>"Unknown"};S=t.icon2?`\n \n \n `:t.textLabel?`\n \n ${t.textLabel}\n `:``}let C="",M=o?.utilization_pct??null;if(null==M&&e.breaker_rating_a){const t=e.entities?.current,i=t?n.states[t]:null,r=i?Math.abs(parseFloat(i.state)||0):0;M=Math.round(r/e.breaker_rating_a*1e3)/10}if(null!=M){C=`=80?"utilization-warning":"utilization-normal"}">${Math.round(M)}%`}const k=``,T=!1!==e.is_user_controllable&&!!e.entities?.switch?`
\n ${i(f?"grid.on":"grid.off")}\n \n
`:`${f?"ON":"OFF"}`;return`\n
\n ${v?`${v}`:""}\n ${C}\n ${y}\n ${S}\n ${T}\n \n ${x}\n \n ${k}\n \n
\n `}function Vk(t,e,n,i,r){const o=e.entities?.power,a=o?n.states[o]:null,s=a&&parseFloat(a.state)||0,l=e.device_type===u||s<0,d=e.entities?.switch,h=d?n.states[d]:null,p=ne(0,r,h?"on"===h.state:(a?.attributes?.relay_state||e.relay_state)===c,l),f=Rt(t);return`\n
\n
\n
\n
\n
\n `}function Wk(t){return`
${Rt(t)}
`}function Uk(t,e){const n=e.hysteresisPx??48,i=new WeakSet;let r=null;const o=t=>{const n=t.querySelector(e.nameSelector);return!!n&&(0!==t.clientWidth&&(n.clientWidth<1||n.scrollWidth>n.clientWidth+1))},a=()=>{const i=t.querySelectorAll(e.rowSelector);if(0===i.length)return;const a=i[0].clientWidth;if(0===a)return;if(null!==r){if(a>r+n){for(const t of i)t.classList.remove(e.foldClass);r=null}else for(const t of i)t.classList.add(e.foldClass);return}let s=!1;for(const t of i)if(o(t)){s=!0;break}if(s){for(const t of i)t.classList.add(e.foldClass);r=a}},s=new ResizeObserver(()=>a()),l=()=>{const n=t.querySelectorAll(e.rowSelector);for(const t of n)i.has(t)||(i.add(t),s.observe(t));requestAnimationFrame(()=>a())};l();const c=new MutationObserver(t=>{(t=>{const n=t=>{for(const n of t)if(n instanceof Element){if(n.matches(e.rowSelector))return!0;if(n.querySelector(e.rowSelector))return!0}return!1};for(const e of t)if(n(e.addedNodes)||n(e.removedNodes))return!0;return!1})(t)&&l()});return c.observe(t,{childList:!0,subtree:!0}),()=>{s.disconnect(),c.disconnect()}}function Gk(t,e,n){const i=t.entities?.switch,r=i?e.states[i]:null,o=t.entities?.power,a=o?e.states[o]:null,s=r?"on"===r.state:(a?.attributes?.relay_state||t.relay_state)===c;let l;if("current"===(n.chart_metric||"power")){const n=t.entities?.current,i=n?e.states[n]:null;l=i?Math.abs(parseFloat(i.state)||0):0}else l=a?Math.abs(parseFloat(a.state)||0):0;return{isOn:s,value:l}}function qk(t,e){if(t.always_on)return"always_on";const n=t.entities?.select,i=n?e.states[n]:null;return i?i.state:"unknown"}function jk(t,e,n,i){const r=Gk(t,n,i),o=Gk(e,n,i);return r.isOn&&!o.isOn?-1:!r.isOn&&o.isOn?1:o.value-r.value}function Xk(t,e,n){return t.sort((t,i)=>jk(t[1],i[1],e,n))}function Yk(t){return t.entities?.current??t.entities?.power??""}class Zk{constructor(t){this._expandedUuids=new Set,this._searchQuery="",this._container=null,this._clickHandler=null,this._inputHandler=null,this._graphSettingsHandler=null,this._hass=null,this._topology=null,this._config=null,this._monitoringStatus=null,this._viewName=null,this._columns=1,this._foldUnobserve=null,this._ctrl=t}setColumns(t){const e=Math.max(1,Math.min(3,Math.floor(t)));this._columns=e}setInitialExpansion(t){this._expandedUuids=new Set(t)}setInitialSearchQuery(t){this._searchQuery=t}setViewName(t){this._viewName=t}renderActivityView(t,e,n,i,r,o){this._unbindEvents(),this._hass=e,this._topology=n,this._config=i,this._monitoringStatus=r;const a=Xk(Object.entries(n.circuits),e,i);let s=o+Fk(this._searchQuery);s+=`
`;for(const[t,n]of a){const o=ee(r,Yk(n)),a=qk(n,e),l=this._expandedUuids.has(t);s+=`
`,s+=$k(t,n,e,i,o,a,l),l&&(s+=Vk(t,n,e,0,o)),s+="
"}s+="
",s+="",t.innerHTML=s;const l=t.querySelector("span-side-panel");l&&(l.hass=e,l.errorStore=this._ctrl.errorStore),this._bindEvents(t),this._searchQuery&&this._applyFilter(t),this._ctrl.updateDOM(t),this._attachFoldObserver(t)}renderAreaView(t,e,n,r,o,a){this._unbindEvents(),this._hass=e,this._topology=n,this._config=r,this._monitoringStatus=o;const s=i("list.unassigned_area"),l=new Map;for(const[t,e]of Object.entries(n.circuits)){const n=e.area??s,i=l.get(n);i?i.push([t,e]):l.set(n,[[t,e]])}const c=[...l.keys()].sort((t,e)=>t===s?1:e===s?-1:t.localeCompare(e));let u=a+Fk(this._searchQuery);u+=`
`;for(const t of c){const n=l.get(t);if(!n)continue;const i=Xk(n,e,r);u+=Wk(t);for(const[t,n]of i){const i=ee(o,Yk(n)),a=qk(n,e),s=this._expandedUuids.has(t);u+=`
`,u+=$k(t,n,e,r,i,a,s),s&&(u+=Vk(t,n,e,0,i)),u+="
"}}u+="
",u+="",t.innerHTML=u;const d=t.querySelector("span-side-panel");d&&(d.hass=e,d.errorStore=this._ctrl.errorStore),this._bindEvents(t),this._searchQuery&&this._applyFilter(t),this._ctrl.updateDOM(t),this._attachFoldObserver(t)}updateCollapsedRows(t,e,n,r){const o=Yt(r),a="current"===o.entityRole,s=t.querySelectorAll(".list-row[data-row-uuid]");for(const t of s){const s=t.dataset.rowUuid;if(!s)continue;const l=n.circuits[s];if(!l)continue;const{isOn:c,value:u}=Gk(l,e,r),d=t.querySelector(".list-power-value");if(d)if(c)if(a)d.innerHTML=`${o.format(u)}A`;else{const t=l.entities?.power,n=t?e.states[t]:null,i=n&&parseFloat(n.state)||0;d.innerHTML=`${Ut(i)}${Wt(i)}`}else d.innerHTML="";const h=t.querySelector(".toggle-pill");if(h){h.classList.toggle("toggle-on",c),h.classList.toggle("toggle-off",!c);const t=h.querySelector(".toggle-label");t&&(t.textContent=i(c?"grid.on":"grid.off"))}const p=t.querySelector(".list-status-badge");p&&(p.textContent=c?"ON":"OFF",p.classList.toggle("list-status-on",c),p.classList.toggle("list-status-off",!c)),t.classList.toggle("circuit-off",!c)}!function(t,e,n,i){const r=t.querySelector(".list-view");if(r)for(const t of function(t,e){let n={anchor:null,units:[]};const i=[n];for(const r of[...t.children])if(r.classList.contains("area-header"))n={anchor:r,units:[]},i.push(n);else if(r.classList.contains("list-cell")){const t=r.dataset.cellUuid,i=t?e.circuits[t]:void 0;t&&i&&n.units.push({cell:r,uuid:t,circuit:i})}return i}(r,n)){if(t.units.length<2)continue;const n=[...t.units].sort((t,n)=>jk(t.circuit,n.circuit,e,i));if(!n.some((e,n)=>e.uuid!==t.units[n].uuid))continue;let o=t.anchor;for(const t of n)o?o.after(t.cell):r.prepend(t.cell),o=t.cell}}(t,e,n,r)}stop(){this._unbindEvents(),null===this._viewName&&(this._expandedUuids.clear(),this._searchQuery=""),this._hass=null,this._topology=null,this._config=null,this._monitoringStatus=null}_dispatchFavoritesViewState(){if(!this._viewName||!this._container)return;const t={view:this._viewName,expanded:[...this._expandedUuids],searchQuery:this._searchQuery};this._container.dispatchEvent(new CustomEvent("favorites-view-state-changed",{detail:t,bubbles:!0,composed:!0}))}_bindEvents(t){this._container=t,this._clickHandler=e=>{const n=e.target;if(!n)return;const i=n.closest(".list-expand-toggle");if(i){const t=i.dataset.expandUuid;return void(t&&this._toggleExpand(t))}if(n.closest(".gear-icon"))return void this._ctrl.onGearClick(e,t);if(n.closest(".toggle-pill"))return void this._ctrl.onToggleClick(e,t);if(n.closest(".list-search-clear")){const e=t.querySelector(".list-search");return void(e&&(e.value="",e.dispatchEvent(new Event("input",{bubbles:!0}))))}const r=n.closest(".unit-btn");if(r){const e=r.dataset.unit;e&&t.dispatchEvent(new CustomEvent("unit-changed",{detail:e,bubbles:!0,composed:!0}))}},this._inputHandler=e=>{const n=e.target;n&&n.classList.contains("list-search")&&(this._searchQuery=n.value.toLowerCase(),this._applyFilter(t),this._dispatchFavoritesViewState())},this._graphSettingsHandler=()=>{this._ctrl.onGraphSettingsChanged(t).then(()=>{this._ctrl.updateDOM(t)}).catch(()=>{})},t.addEventListener("click",this._clickHandler),t.addEventListener("input",this._inputHandler),t.addEventListener("graph-settings-changed",this._graphSettingsHandler);const e=t.querySelector(".slide-confirm");e&&(this._ctrl.bindSlideConfirm(e,t),t.classList.add("switches-disabled"))}_unbindEvents(){this._container&&(this._clickHandler&&this._container.removeEventListener("click",this._clickHandler),this._inputHandler&&this._container.removeEventListener("input",this._inputHandler),this._graphSettingsHandler&&this._container.removeEventListener("graph-settings-changed",this._graphSettingsHandler)),this._foldUnobserve&&(this._foldUnobserve(),this._foldUnobserve=null),this._container=null,this._clickHandler=null,this._inputHandler=null,this._graphSettingsHandler=null}_attachFoldObserver(t){this._foldUnobserve&&(this._foldUnobserve(),this._foldUnobserve=null),this._foldUnobserve=Uk(t,{rowSelector:".list-row",nameSelector:".list-circuit-name",foldClass:"is-folded"})}_applyFilter(t){const e=t.querySelector(".list-search-clear");e&&(e.style.display=this._searchQuery?"":"none");const n=t.querySelectorAll(".list-cell[data-cell-uuid]");for(const t of n){const e=t.querySelector(".list-circuit-name"),n=(e?.textContent?.toLowerCase()??"").includes(this._searchQuery);t.style.display=n?"":"none"}const i=t.querySelectorAll(".area-header");for(const t of i){let e=!1,n=t.nextElementSibling;for(;n&&!n.classList.contains("area-header");){if(n.classList.contains("list-cell")&&"none"!==n.style.display){e=!0;break}n=n.nextElementSibling}t.style.display=e?"":"none"}}_toggleExpand(t){if(!(this._container&&this._hass&&this._topology&&this._config))return;const e=Ok(t),n=this._container.querySelector(`.list-cell[data-cell-uuid="${e}"]`);if(!n)return;const i=n.querySelector(`.list-row[data-row-uuid="${e}"]`),r=n.querySelector(`.list-expand-toggle[data-expand-uuid="${e}"]`);if(i){if(this._expandedUuids.has(t)){this._expandedUuids.delete(t);const o=n.querySelector(`.list-expanded-content[data-expanded-uuid="${e}"]`);o&&o.remove(),r&&r.classList.remove("expanded"),i.classList.remove("list-row-expanded")}else{this._expandedUuids.add(t);const e=this._topology.circuits[t];if(!e)return;const n=ee(this._monitoringStatus,Yk(e)),o=Vk(t,e,this._hass,this._config,n);i.insertAdjacentHTML("afterend",o),r&&r.classList.add("expanded"),i.classList.add("list-row-expanded"),this._ctrl.updateDOM(this._container)}this._dispatchFavoritesViewState()}}}async function Kk(t,e){const[n,i,r]=await Promise.all([t.callWS({type:"config/area_registry/list"}),t.callWS({type:"config/entity_registry/list"}),t.callWS({type:"config/device_registry/list"})]),o=new Map;for(const t of n)o.set(t.area_id,t.name);const a=new Map;for(const t of i)t.area_id&&a.set(t.entity_id,t.area_id);const s=new Map;for(const t of r)s.set(t.id,t.area_id);let l;const c=e.panel_device_id??e.device_id;if(c){const t=s.get(c);t&&(l=o.get(t))}for(const t of Object.values(e.circuits)){let e;for(const n of Object.values(t.entities)){if(!n)continue;const t=a.get(n);if(t){e=o.get(t);break}}e||(e=l),t.area=e}}class Qk{constructor(){this._persistent=new Map,this._transient=null,this._transientTimer=null,this._subscribers=new Set,this._watchedPanels=new Map}add(t){const e={...t,timestamp:Date.now()};if(e.persistent)this._persistent.set(e.key,e);else{this._clearTransient(),this._transient=e;const t=e.ttl??5e3;this._transientTimer=setTimeout(()=>{this._transient=null,this._transientTimer=null,this._notify()},t)}this._notify()}remove(t){if(this._persistent.has(t))return this._persistent.delete(t),void this._notify();this._transient?.key===t&&(this._clearTransient(),this._notify())}clear(t){void 0===t?(this._persistent.clear(),this._clearTransient(),this._watchedPanels.clear()):!0===t.persistent?this._persistent.clear():!1===t.persistent&&this._clearTransient(),this._notify()}get active(){const t=[...this._persistent.values()];return null!==this._transient&&t.push(this._transient),t}hasPersistent(t){return this._persistent.has(t)}hasAnyPanelOffline(){for(const t of this._persistent.keys())if("panel-offline"===t||t.startsWith("panel-offline:"))return!0;return!1}subscribe(t){return this._subscribers.add(t),()=>{this._subscribers.delete(t)}}watchPanelStatus(t){this.watchPanelStatuses([{entityId:t,panelName:null}])}watchPanelStatuses(t){const e=this._watchedPanels,n=new Map;for(const i of t){const t=e.get(i.entityId);n.set(i.entityId,{panelName:i.panelName??null,wasOffline:t?.wasOffline??!1})}const i=this._isSingleUnnamed(e),r=this._isSingleUnnamed(n);for(const t of e.keys()){n.has(t)&&i===r||this._persistent.delete(this._offlineKey(t,i))}this._watchedPanels=n,this._notify()}clearPanelStatusWatch(){if(0===this._watchedPanels.size)return;const t=this._isSingleUnnamed(this._watchedPanels);for(const e of this._watchedPanels.keys())this._persistent.delete(this._offlineKey(e,t));this._watchedPanels.clear(),this._notify()}updateHass(t){if(0===this._watchedPanels.size)return;const e=this._isSingleUnnamed(this._watchedPanels);for(const[n,o]of this._watchedPanels){const a=t.states[n]?.state,s="on"===a,l=this._offlineKey(n,e),c=this._reconnectKey(n,e);if(s){const t=o.wasOffline;o.wasOffline=!1,this.remove(l),t&&this.add({key:c,level:"info",message:null===o.panelName?i("error.panel_reconnected"):r("error.panel_reconnected_named",{name:o.panelName}),persistent:!1})}else o.wasOffline=!0,this.hasPersistent(l)||this.add({key:l,level:"error",message:null===o.panelName?i("error.panel_offline"):r("error.panel_offline_named",{name:o.panelName}),persistent:!0})}}dispose(){this._clearTransient(),this._persistent.clear(),this._subscribers.clear(),this._watchedPanels.clear()}_isSingleUnnamed(t){if(1!==t.size)return!1;for(const e of t.values())return null===e.panelName;return!1}_offlineKey(t,e){return e?"panel-offline":`panel-offline:${t}`}_reconnectKey(t,e){return e?"panel-reconnected":`panel-reconnected:${t}`}_clearTransient(){null!==this._transientTimer&&(clearTimeout(this._transientTimer),this._transientTimer=null),this._transient=null}_notify(){for(const t of this._subscribers)try{t()}catch(t){console.warn("SPAN Panel: error-store subscriber threw",t)}}}function Jk(t){let e=0;for(const n of Object.values(t))if(n)for(const t of n.tabs)t>e&&(e=t);return e>0?e+e%2:0}function tT(t){return t?{id:t.id,name:t.name,name_by_user:t.name_by_user,config_entry_id:t.config_entry_id,identifiers:t.identifiers,via_device_id:t.via_device_id,sw_version:t.sw_version,model:t.model}:null}const eT="favorites-changed";async function nT(t,e,n={}){const i=await t.callWS({type:"call_service",domain:l,service:e,service_data:n,return_response:!0});return i?.response??null}const iT=Object.keys(m).filter(t=>"unknown"!==t&&"always_on"!==t);class rT extends HTMLElement{constructor(){super(),this.errorStore=null,this.attachShadow({mode:"open"}),this._hass=null,this._config=null,this._debounceTimers={}}set hass(t){this._hass=t,this.hasAttribute("open")&&this._config&&this._updateLiveState()}get hass(){return this._hass}disconnectedCallback(){this._clearDebounceTimers(),this._config=null}open(t){this._config=t,this._render(),this.offsetHeight,this.setAttribute("open",""),this.setAttribute("data-mode",this._modeFor(t))}close(){this._clearDebounceTimers(),this.removeAttribute("open"),this.removeAttribute("data-mode"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_clearDebounceTimers(){for(const t of Object.keys(this._debounceTimers))clearTimeout(this._debounceTimers[t]);this._debounceTimers={}}_modeFor(t){return t.favoritesMode?"favorites":t.panelMode?"panel":t.subDeviceMode?"subDevice":"circuit"}_render(){const t=this._config;if(!t)return;const e=this.shadowRoot;if(!e)return;e.innerHTML="";const n=document.createElement("style");n.textContent='\n :host {\n display: block;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 360px;\n max-width: 90vw;\n z-index: 1000;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n pointer-events: none;\n }\n :host([open]) {\n transform: translateX(0);\n pointer-events: auto;\n }\n\n .backdrop {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: -1;\n }\n :host([open]) .backdrop {\n display: block;\n }\n\n .panel {\n height: 100%;\n background: var(--card-background-color, #fff);\n border-left: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n }\n .panel-header .title {\n font-size: 18px;\n font-weight: 500;\n color: var(--primary-text-color, #212121);\n margin: 0;\n }\n .panel-header .subtitle {\n font-size: 13px;\n color: var(--secondary-text-color, #727272);\n margin: 2px 0 0 0;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color, #727272);\n padding: 4px;\n line-height: 1;\n font-size: 20px;\n }\n\n .panel-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .section {\n margin-bottom: 20px;\n }\n .section-label {\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--secondary-text-color, #727272);\n margin: 0 0 8px 0;\n letter-spacing: 0.5px;\n }\n\n .field-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 0;\n }\n .field-label {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n }\n\n select {\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n }\n\n input[type="number"] {\n width: 72px;\n padding: 6px 8px;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 4px;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 14px;\n text-align: right;\n }\n input[type="number"]:disabled {\n opacity: 0.5;\n }\n\n .radio-group {\n display: flex;\n gap: 16px;\n padding: 8px 0;\n }\n .radio-group label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n cursor: pointer;\n }\n\n .horizon-bar {\n display: flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n margin-top: 4px;\n }\n .horizon-segment {\n flex: 1;\n padding: 6px 0;\n text-align: center;\n font-size: 13px;\n cursor: pointer;\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n line-height: 1.4;\n }\n .horizon-segment:last-child {\n border-right: none;\n }\n .horizon-segment:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .horizon-segment.active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n .horizon-segment.referenced {\n box-shadow: inset 0 -3px 0 var(--primary-color, #03a9f4);\n }\n\n .unit-toggle {\n display: inline-flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n }\n .unit-btn {\n padding: 4px 10px;\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.15s ease, color 0.15s ease;\n }\n .unit-btn:last-child {\n border-right: none;\n }\n .unit-btn:hover:not(.unit-active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .unit-btn.unit-active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .fav-heart {\n background: none;\n border: 1px solid var(--divider-color, #e0e0e0);\n color: var(--secondary-text-color, #727272);\n border-radius: 4px;\n padding: 2px 6px;\n cursor: pointer;\n font-size: 0.9em;\n margin-right: 6px;\n line-height: 1;\n display: inline-flex;\n align-items: center;\n }\n .fav-heart.active {\n color: var(--primary-color, #03a9f4);\n border-color: var(--primary-color, #03a9f4);\n }\n .fav-heart:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .fav-heart span-icon {\n --mdc-icon-size: 16px;\n }\n\n .panel-mode-info {\n font-size: 14px;\n color: var(--primary-text-color, #212121);\n line-height: 1.6;\n }\n .panel-mode-info p {\n margin: 0 0 12px 0;\n }\n\n',e.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),e.appendChild(i);const r=document.createElement("div");r.className="panel",e.appendChild(r),t.favoritesMode?this._renderFavoritesMode(r):t.panelMode?this._renderPanelMode(r):t.subDeviceMode?this._renderSubDeviceMode(r,t):this._renderCircuitMode(r,t)}_renderPanelMode(t){const e=this._config,n=this._createHeader(i("sidepanel.graph_settings"),i("sidepanel.global_defaults"));t.appendChild(n);const r=document.createElement("div");r.className="panel-body";const o=e.graphSettings,l=e.topology,c=o?.global_horizon??a,u=o?.circuits??{};r.appendChild(this._buildListColumnsSection());const d=document.createElement("div");d.className="section";const h=document.createElement("div");h.className="section-label",h.textContent=i("sidepanel.graph_horizon"),d.appendChild(h);const p=document.createElement("div");p.className="field-row";const g=document.createElement("span");g.className="field-label",g.textContent=i("sidepanel.global_default"),p.appendChild(g);const v=document.createElement("select");for(const t of Object.keys(s)){const e=document.createElement("option");e.value=t;const n=`horizon.${t}`,r=i(n);e.textContent=r!==n?r:t,t===c&&(e.selected=!0),v.appendChild(e)}if(v.addEventListener("change",()=>{const t={horizon:v.value};e.configEntryId&&(t.config_entry_id=e.configEntryId),this._callDomainService("set_graph_time_horizon",t).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(t=>{console.warn("SPAN Panel: graph horizon service failed",t),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:i("error.graph_horizon_failed"),persistent:!1})})}),p.appendChild(v),d.appendChild(p),r.appendChild(d),l?.circuits){const t=document.createElement("div");t.className="section";const n=document.createElement("div");n.className="section-label",n.textContent=i("sidepanel.circuit_scales"),t.appendChild(n);const o=Object.entries(l.circuits).sort(([,t],[,e])=>(t.name||"").localeCompare(e.name||""));for(const[n,i]of o){const r=this._buildPanelModeCircuitRow(n,i,u[n],c,e.configEntryId??null,e.showFavorites??!1,e.favoritePanelDeviceId,e.favoriteCircuitUuids);t.appendChild(r)}r.appendChild(t)}const m=o?.sub_devices??{};if(l?.sub_devices){const t=document.createElement("div");t.className="section";const n=document.createElement("div");n.className="section-label",n.textContent=i("sidepanel.subdevice_scales"),t.appendChild(n);const o=Object.entries(l.sub_devices).sort(([,t],[,e])=>(t.name||"").localeCompare(e.name||""));for(const[n,r]of o){const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");if(a.className="field-label",a.textContent=r.name||n,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",o.appendChild(a),e.showFavorites&&e.favoritePanelDeviceId){const t=this._buildSubDeviceFavoriteHeart(r.entities,e.favoriteSubDeviceIds?.has(n)??!1);t&&o.appendChild(t)}const l=m[n]||{horizon:c,has_override:!1},u=l.has_override?l.horizon:c,d=document.createElement("select");d.dataset.subdevId=n;for(const t of Object.keys(s)){const e=document.createElement("option");e.value=t;const n=`horizon.${t}`,r=i(n);e.textContent=r!==n?r:t,t===u&&(e.selected=!0),d.appendChild(e)}if(d.addEventListener("change",()=>{this._debounce(`subdev-${n}`,f,()=>{const t={subdevice_id:n,horizon:d.value};e.configEntryId&&(t.config_entry_id=e.configEntryId),this._callDomainService("set_subdevice_graph_horizon",t).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(t=>{console.warn("SPAN Panel: graph horizon service failed",t),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:i("error.graph_horizon_failed"),persistent:!1})})})}),o.appendChild(d),l.has_override){const t=document.createElement("button");t.textContent="↺",t.title=i("sidepanel.reset_to_global"),Object.assign(t.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),t.addEventListener("click",()=>{const r={subdevice_id:n};e.configEntryId&&(r.config_entry_id=e.configEntryId),this._callDomainService("clear_subdevice_graph_horizon",r).then(()=>{d.value=c,t.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(t=>{console.warn("SPAN Panel: graph horizon service failed",t),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:i("error.graph_horizon_failed"),persistent:!1})})}),o.appendChild(t)}t.appendChild(o)}r.appendChild(t)}t.appendChild(r)}_buildPanelModeCircuitRow(t,e,n,r,o,a,l,c){const u=document.createElement("div");u.className="field-row";const d=document.createElement("span");if(d.className="field-label",d.textContent=e.name||t,d.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",u.appendChild(d),a&&l){const n=this._buildFavoriteHeart(e.entities,c?.has(t)??!1);n&&u.appendChild(n)}const h=n||{horizon:r,has_override:!1},p=h.has_override?h.horizon:r,g=document.createElement("select");g.dataset.uuid=t;for(const t of Object.keys(s)){const e=document.createElement("option");e.value=t;const n=`horizon.${t}`,r=i(n);e.textContent=r!==n?r:t,t===p&&(e.selected=!0),g.appendChild(e)}if(g.addEventListener("change",()=>{this._debounce(`circuit-${t}`,f,()=>{const e={circuit_id:t,horizon:g.value};o&&(e.config_entry_id=o),this._callDomainService("set_circuit_graph_horizon",e).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(t=>{console.warn("SPAN Panel: graph horizon service failed",t),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:i("error.graph_horizon_failed"),persistent:!1})})})}),u.appendChild(g),h.has_override){const e=document.createElement("button");e.textContent="↺",e.title=i("sidepanel.reset_to_global"),Object.assign(e.style,{background:"none",border:"1px solid var(--divider-color, #e0e0e0)",color:"var(--primary-text-color)",borderRadius:"4px",padding:"3px 6px",cursor:"pointer",marginLeft:"4px",fontSize:"0.85em"}),e.addEventListener("click",()=>{const n={circuit_id:t};o&&(n.config_entry_id=o),this._callDomainService("clear_circuit_graph_horizon",n).then(()=>{g.value=r,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(t=>{console.warn("SPAN Panel: graph horizon service failed",t),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:i("error.graph_horizon_failed"),persistent:!1})})}),u.appendChild(e)}return u}_renderFavoritesMode(t){const e=this._config,n=this._createHeader(i("sidepanel.graph_settings"),i("sidepanel.favorites_subtitle"));t.appendChild(n);const r=document.createElement("div");r.className="panel-body",r.appendChild(this._buildListColumnsSection());for(const t of e.perPanelSections)r.appendChild(this._buildFavoritesPanelSection(t));t.appendChild(r)}_buildFavoritesPanelSection(t){const e=document.createElement("div");e.className="section";const n=document.createElement("div");n.className="section-label",n.textContent=t.panelName,e.appendChild(n);const i=t.graphSettings?.global_horizon??a,r=t.graphSettings?.circuits??{},o=function(t){const e=t.circuits??{};return Object.entries(e).map(([t,e])=>({uuid:t,circuit:e})).sort((t,e)=>(t.circuit.name||"").localeCompare(e.circuit.name||""))}(t.topology);for(const{uuid:n,circuit:a}of o){const o=this._buildPanelModeCircuitRow(n,a,r[n],i,t.configEntryId,!0,t.panelDeviceId,t.favoriteCircuitUuids);e.appendChild(o)}return e}_renderCircuitMode(t,e){const n=`${Rt(String(e.breaker_rating_a))}A · ${Rt(String(e.voltage))}V · Tabs [${Rt(String(e.tabs))}]`,i=this._createHeader(Rt(e.name),n);t.appendChild(i);const r=document.createElement("div");r.className="panel-body",t.appendChild(r),this._renderRelaySection(r,e),e.showFavorites&&this._renderFavoriteSection(r,e),this._renderSheddingSection(r,e),this._renderGraphHorizonSection(r,e),e.showMonitoring&&this._renderMonitoringSection(r,e)}_favoriteEntityId(t){return t?.current??t?.power??null}_subDeviceFavoriteEntityId(t){if(!t)return null;let e=null;for(const[n,i]of Object.entries(t)){if("sensor"===i.domain)return n;e||(e=n)}return e}_buildSubDeviceFavoriteHeart(t,e){const n=this._subDeviceFavoriteEntityId(t);return n?this._buildHeartButton(n,e):null}_buildListColumnsSection(){const t=document.createElement("div");t.className="section";const e=document.createElement("div");e.className="section-label",e.textContent=i("sidepanel.list_view_columns"),t.appendChild(e);const n=document.createElement("div");n.className="field-row";const r=document.createElement("span");r.className="field-label",r.textContent=i("sidepanel.columns"),n.appendChild(r);const o=Bt(),a=document.createElement("div");a.className="unit-toggle";for(const t of[1,2,3]){const e=document.createElement("button");e.type="button",e.className="unit-btn"+(t===o?" unit-active":""),e.dataset.columns=String(t),e.textContent=String(t),e.addEventListener("click",()=>{Ft(t);for(const t of a.querySelectorAll(".unit-btn"))t.classList.toggle("unit-active",t===e);this.dispatchEvent(new CustomEvent("list-columns-changed",{detail:t,bubbles:!0,composed:!0}))}),a.appendChild(e)}return n.appendChild(a),t.appendChild(n),t}_buildFavoriteHeart(t,e){const n=this._favoriteEntityId(t);return n?this._buildHeartButton(n,e):(console.warn("SPAN Panel: circuit has no current/power sensor; favorite heart suppressed"),null)}_buildHeartButton(t,e){const n=document.createElement("button");n.type="button",n.className=e?"fav-heart active":"fav-heart",n.dataset.role="fav-heart",n.title=i("sidepanel.save_to_favorites"),n.setAttribute("role","switch"),n.setAttribute("aria-checked",String(e)),n.setAttribute("aria-label",i("sidepanel.save_to_favorites"));const r=document.createElement("span-icon");return r.setAttribute("icon",e?"mdi:heart":"mdi:heart-outline"),n.appendChild(r),n.addEventListener("click",e=>{e.stopPropagation(),this._toggleFavoriteEntity(n,r,t).catch(()=>{})}),n}async _toggleFavoriteEntity(t,e,n){if(!this._hass)return;const r=t.classList.contains("active"),o=!r;t.classList.toggle("active",o),e.setAttribute("icon",o?"mdi:heart":"mdi:heart-outline"),t.setAttribute("aria-checked",String(o));try{o?await async function(t,e){const n=await nT(t,"add_favorite",{entity_id:e});return document.dispatchEvent(new CustomEvent(eT)),n?.favorites??{}}(this._hass,n):await async function(t,e){const n=await nT(t,"remove_favorite",{entity_id:e});return document.dispatchEvent(new CustomEvent(eT)),n?.favorites??{}}(this._hass,n)}catch(n){throw t.classList.toggle("active",r),e.setAttribute("icon",r?"mdi:heart":"mdi:heart-outline"),t.setAttribute("aria-checked",String(r)),console.warn("SPAN Panel: favorite toggle failed",n),this.errorStore?.add({key:"service:favorites",level:"error",message:i("error.favorites_toggle_failed"),persistent:!1}),n}}_renderFavoriteSection(t,e){const n=this._favoriteEntityId(e.entities);n&&this._appendFavoriteHeartSection(t,n,!0===e.isFavorite)}_appendFavoriteHeartSection(t,e,n){const r=document.createElement("div");r.className="section",r.innerHTML=``;const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=i("sidepanel.save_to_favorites"),o.appendChild(a),o.appendChild(this._buildHeartButton(e,n)),r.appendChild(o),t.appendChild(r)}_renderSubDeviceMode(t,e){const n=this._createHeader(Rt(e.name),Rt(e.deviceType));t.appendChild(n);const i=document.createElement("div");i.className="panel-body",t.appendChild(i),e.showFavorites&&this._renderSubDeviceFavoriteSection(i,e),this._renderSubDeviceHorizonSection(i,e)}_renderSubDeviceFavoriteSection(t,e){const n=this._subDeviceFavoriteEntityId(e.entities);n&&this._appendFavoriteHeartSection(t,n,!0===e.isFavorite)}_renderSubDeviceHorizonSection(t,e){const n=document.createElement("div");n.className="section";const r=document.createElement("div");r.className="section-label",r.textContent=i("sidepanel.graph_horizon"),n.appendChild(r);const o=e.graphHorizonInfo,l=!0===o?.has_override,c=o?.horizon||a,u=o?.globalHorizon||a,d=document.createElement("div");d.className="horizon-bar";const h=[{key:"global",label:i("sidepanel.global")}];for(const t of Object.keys(s))h.push({key:t,label:t});const p=l?c:"global",f=t=>{for(const e of d.querySelectorAll(".horizon-segment")){const n=e.dataset.horizon;e.classList.toggle("active",n===t),e.classList.toggle("referenced","global"===t&&n===u)}};for(const{key:t,label:n}of h){const r=document.createElement("button");r.type="button",r.className="horizon-segment",r.dataset.horizon=t,r.textContent=n,r.classList.toggle("active",t===p),r.classList.toggle("referenced","global"===p&&t===u),r.addEventListener("click",()=>{if(r.classList.contains("active"))return;const n={subdevice_id:e.subDeviceId};e.configEntryId&&(n.config_entry_id=e.configEntryId),"global"===t?(f("global"),this._callDomainService("clear_subdevice_graph_horizon",n).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(t=>{console.warn("SPAN Panel: graph horizon service failed",t),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:i("error.graph_horizon_failed"),persistent:!1})})):(f(t),this._callDomainService("set_subdevice_graph_horizon",{...n,horizon:t}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(t=>{console.warn("SPAN Panel: graph horizon service failed",t),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:i("error.graph_horizon_failed"),persistent:!1})}))}),d.appendChild(r)}n.appendChild(d),t.appendChild(n)}_createHeader(t,e){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div"),r=Rt(t),o=Rt(e);i.innerHTML=`
${r}
`+(o?`
${o}
`:"");const a=document.createElement("button");return a.className="close-btn",a.innerHTML="✕",a.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(a),n}_renderRelaySection(t,e){if(!1===e.is_user_controllable||!e.entities?.switch)return;const n=document.createElement("div");n.className="section",n.innerHTML=``;const r=document.createElement("div");r.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent=i("sidepanel.breaker");const a=document.createElement("span-switch");a.dataset.role="relay-toggle";const s=e.entities.switch,l=this._hass?.states?.[s]?.state;"on"===l&&a.setAttribute("checked",""),a.addEventListener("change",()=>{const t=a.hasAttribute("checked")||a.checked;this._callService("switch",t?"turn_on":"turn_off",{entity_id:s}).catch(t=>{console.warn("SPAN Panel: relay toggle failed",t),this.errorStore?.add({key:"service:relay",level:"error",message:i("error.relay_failed"),persistent:!1})})}),r.appendChild(o),r.appendChild(a),n.appendChild(r),t.appendChild(n)}_renderSheddingSection(t,e){if(!e.entities?.select)return;const n=document.createElement("div");n.className="section",n.innerHTML=``;const r=document.createElement("div");r.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent=i("sidepanel.priority_label");const a=document.createElement("select");a.dataset.role="shedding-select";const s=e.entities.select,l=this._hass?.states?.[s]?.state||"";for(const t of iT){const e=m[t];if(!e)continue;const n=document.createElement("option");n.value=t,n.textContent=i(`shedding.select.${t}`)||e.label(),t===l&&(n.selected=!0),a.appendChild(n)}a.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:s,option:a.value}).catch(t=>{console.warn("SPAN Panel: shedding update failed",t),this.errorStore?.add({key:"service:shedding",level:"error",message:i("error.shedding_failed"),persistent:!1})})}),r.appendChild(o),r.appendChild(a),n.appendChild(r),t.appendChild(n)}_renderGraphHorizonSection(t,e){const n=document.createElement("div");n.className="section";const r=document.createElement("div");r.className="section-label",r.textContent=i("sidepanel.graph_horizon"),n.appendChild(r);const o=e.graphHorizonInfo,l=!0===o?.has_override,c=o?.horizon||a,u=o?.globalHorizon||a,d=document.createElement("div");d.className="horizon-bar";const h=[{key:"global",label:i("sidepanel.global")}];for(const t of Object.keys(s))h.push({key:t,label:t});const p=l?c:"global",f=t=>{for(const e of d.querySelectorAll(".horizon-segment")){const n=e.dataset.horizon;e.classList.toggle("active",n===t),e.classList.toggle("referenced","global"===t&&n===u)}};for(const{key:t,label:n}of h){const r=document.createElement("button");r.type="button",r.className="horizon-segment",r.dataset.horizon=t,r.textContent=n,r.classList.toggle("active",t===p),r.classList.toggle("referenced","global"===p&&t===u),r.addEventListener("click",()=>{if(r.classList.contains("active"))return;const n={circuit_id:e.uuid};e.configEntryId&&(n.config_entry_id=e.configEntryId),"global"===t?(f("global"),this._callDomainService("clear_circuit_graph_horizon",n).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(t=>{console.warn("SPAN Panel: graph horizon service failed",t),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:i("error.graph_horizon_failed"),persistent:!1})})):(f(t),this._callDomainService("set_circuit_graph_horizon",{...n,horizon:t}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(t=>{console.warn("SPAN Panel: graph horizon service failed",t),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:i("error.graph_horizon_failed"),persistent:!1})}))}),d.appendChild(r)}n.appendChild(d),t.appendChild(n)}_renderMonitoringSection(t,e){const n=document.createElement("div");n.className="section";const r=document.createElement("div");r.className="monitoring-header";const o=document.createElement("div");o.className="section-label",o.textContent=i("sidepanel.monitoring"),o.style.margin="0";const a=document.createElement("span-switch");a.dataset.role="monitoring-toggle";const s=e.monitoringInfo,l=null!=s&&!1!==s.monitoring_enabled;l&&a.setAttribute("checked",""),r.appendChild(o),r.appendChild(a),n.appendChild(r);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",n.appendChild(c);const u=!0===s?.has_override,d=document.createElement("div");d.className="radio-group",d.innerHTML=`\n \n \n `,c.appendChild(d);const h=document.createElement("div");h.dataset.role="threshold-fields",h.style.display=u?"block":"none";const p=s?.continuous_threshold_pct??80,f=s?.spike_threshold_pct??100,g=s?.window_duration_m??15,v=s?.cooldown_duration_m??15;h.appendChild(this._createThresholdRow(i("sidepanel.continuous_pct"),"continuous",p,e)),h.appendChild(this._createThresholdRow(i("sidepanel.spike_pct"),"spike",f,e)),h.appendChild(this._createDurationRow(i("sidepanel.window_duration"),"window-m",g,1,180,"m",e)),h.appendChild(this._createDurationRow(i("sidepanel.cooldown"),"cooldown-m",v,1,180,"m",e)),c.appendChild(h),a.addEventListener("change",()=>{const t=a.checked;c.style.display=t?"block":"none";const n={circuit_id:e.entities?.power||e.uuid,monitoring_enabled:t};e.configEntryId&&(n.config_entry_id=e.configEntryId),this._callDomainService("set_circuit_threshold",n).catch(t=>{console.warn("SPAN Panel: monitoring update failed",t),this.errorStore?.add({key:"service:monitoring",level:"error",message:i("error.threshold_failed"),persistent:!1})})});const m=d.querySelectorAll('input[type="radio"]');for(const t of m)t.addEventListener("change",()=>{const n="custom"===t.value&&t.checked;if(h.style.display=n?"block":"none",!n&&t.checked){const t={circuit_id:e.entities?.power||e.uuid};e.configEntryId&&(t.config_entry_id=e.configEntryId),this._callDomainService("clear_circuit_threshold",t).catch(t=>{console.warn("SPAN Panel: monitoring update failed",t),this.errorStore?.add({key:"service:monitoring",level:"error",message:i("error.threshold_failed"),persistent:!1})})}});t.appendChild(n)}_createThresholdRow(t,e,n,r){const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");a.className="field-label",a.textContent=t;const s=document.createElement("input");return s.type="number",s.min="0",s.max="200",s.value=String(n),s.dataset.role=`threshold-${e}`,s.addEventListener("input",()=>{this._debounce(`threshold-${e}`,f,()=>{const t=this.shadowRoot;if(!t)return;const e=t.querySelector('[data-role="threshold-continuous"]'),n=t.querySelector('[data-role="threshold-spike"]'),o=t.querySelector('[data-role="threshold-window-m"]'),a=t.querySelector('[data-role="threshold-cooldown-m"]'),s={circuit_id:r.entities?.power||r.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:o?Number(o.value):void 0,cooldown_duration_m:a?Number(a.value):void 0};r.configEntryId&&(s.config_entry_id=r.configEntryId),this._callDomainService("set_circuit_threshold",s).catch(t=>{console.warn("SPAN Panel: monitoring update failed",t),this.errorStore?.add({key:"service:monitoring",level:"error",message:i("error.threshold_failed"),persistent:!1})})})}),o.appendChild(a),o.appendChild(s),o}_createDurationRow(t,e,n,r,o,a,s,l=!1){const c=document.createElement("div");c.className="field-row";const u=document.createElement("span");u.className="field-label",u.textContent=t;const d=document.createElement("div"),h=document.createElement("input");h.type="number",h.min=String(r),h.max=String(o),h.value=String(n),h.dataset.role=`threshold-${e}`,l&&(h.disabled=!0);const p=document.createElement("span");return p.textContent=a,d.appendChild(h),d.appendChild(p),l||h.addEventListener("input",()=>{this._debounce(`threshold-${e}`,f,()=>{const t=this.shadowRoot;if(!t)return;const e=t.querySelector('[data-role="threshold-continuous"]'),n=t.querySelector('[data-role="threshold-spike"]'),r=t.querySelector('[data-role="threshold-window-m"]'),o={circuit_id:s.uuid,continuous_threshold_pct:e?Number(e.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:r?Number(r.value):void 0};s.configEntryId&&(o.config_entry_id=s.configEntryId),this._callDomainService("set_circuit_threshold",o).catch(t=>{console.warn("SPAN Panel: monitoring update failed",t),this.errorStore?.add({key:"service:monitoring",level:"error",message:i("error.threshold_failed"),persistent:!1})})})}),c.appendChild(u),c.appendChild(d),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const t=this._config;if(!t.subDeviceMode&&!t.favoritesMode){if(t.entities?.switch){const e=this.shadowRoot?.querySelector('[data-role="relay-toggle"]');if(e){const n=this._hass?.states?.[t.entities.switch]?.state;"on"===n?e.setAttribute("checked",""):e.removeAttribute("checked")}}if(t.entities?.select){const e=this.shadowRoot?.querySelector('[data-role="shedding-select"]');if(e){const n=this._hass?.states?.[t.entities.select]?.state||"";e.value=n}}}}_callService(t,e,n){return this._hass?Promise.resolve(this._hass.callService(t,e,n)):Promise.resolve()}_callDomainService(t,e){return this._hass?this._hass.callWS({type:"call_service",domain:l,service:t,service_data:e}):Promise.resolve()}_debounce(t,e,n){this._debounceTimers[t]&&clearTimeout(this._debounceTimers[t]),this._debounceTimers[t]=setTimeout(()=>{delete this._debounceTimers[t],n()},e)}}try{customElements.get("span-side-panel")||customElements.define("span-side-panel",rT)}catch{}class oT extends It{constructor(){super(...arguments),this._store=null,this._unsub=null,this._errors=[]}set store(t){if(this._store===t)return;this._unsub?.(),this._unsub=null,this._store=t,this._errors=t.active;const e=t;this._unsub=t.subscribe(()=>{this._errors=e.active})}connectedCallback(){if(super.connectedCallback(),this._store&&!this._unsub){const t=this._store;this._errors=t.active,this._unsub=t.subscribe(()=>{this._errors=t.active})}}disconnectedCallback(){super.disconnectedCallback(),this._unsub?.(),this._unsub=null}render(){return 0===this._errors.length?ft:dt`${this._errors.map(t=>dt`