From 8281ad72a064e816ccd3450d8fc0c6bd79477abc Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Thu, 10 Sep 2026 18:42:16 -0700 Subject: [PATCH 1/5] fix(registry): move off the device registry's multi-entry APIs Home Assistant 2026.8 gave every device a single owning config entry. It reports `async_get_device(identifiers=...)` as deprecated and says it stops working in 2027.8: it searches every config entry, but an identifier is unique only within one. The panel's own registration had already moved to `async_get_device_by_identifier`; four lookups had not -- the extension card check, the adopted identity freeze, and both card lookups the curation editor makes. The ambiguity is latent rather than live. A second SPAN entry cannot hold this panel's identifiers today, because the config flow keys entries on the serial every identifier embeds. Were it reached, an adopted device would be frozen onto the other entry's identifier -- permanently, since the freeze is by design irreversible -- the editor would group rows under that entry's device, and an extension row would be admitted around the deferral and mint a nameless card of its own. That guard is a property of the flow rather than of the registry, and the lookups no longer rest on it. Each lookup now takes the entry it is acting for. The scope threads through the extension partition, the adopted identity resolution, the curation rows, the seven entity builders and the five platform setups, which state it beside the panel link they already pass. A wrong scope fails silently -- an adopted entity re-minted under a new unique_id, which the registry reads as a replacement that takes its history, or an extension entity that never appears -- so every site is pinned by a test that registers the cards first: the freeze through the builders, and each platform's setup in test_platform_entry_scope.py. Replacing the scope with a bogus entry at any of the eight sites is caught by the tests aimed at it. The identity-freeze tests that stubbed the registry with a lambda, blind to the scope, now run against the real registry, and three tests cover the other-entry case directly. The two WebSocket commands that take a panel device id found its entry by looping over `DeviceEntry.config_entries`, which 2026.8 reduced to a deprecated shim holding the device's one owner. They now share `resolve_panel_device`, which checks the owning entry's domain. The two copies had drifted: topology took an arbitrary member of the set without checking its domain, so it refused a SPAN identifier on another entry's device as not loaded where the adopted commands answered not a panel. A device id saved before 2026.8 is resolved to the device it became. That release split every device with several config entries into one device per entry, each with a new id, and a SPAN panel was one whenever a helper built on its sensors had linked itself to the panel's device; the dashboard card keeps the id it was configured with. The registry answers such an id with a read-only composite until 2027.8, but its `config_entry_id` is the old device's primary and no sub-device points at it any more, so topology returned no sub-devices -- or, depending on the hash seed, refused a loaded panel as not loaded. The resolver maps the old id to the split a SPAN entry owns through `async_get_devices_for_composite_device_id`, and topology matches sub-devices against that device while still echoing the id it was sent. A child device, which `async_get` can answer with from 2026.9, is refused as not found, as the helper 2026.9 adds for this lookup refuses it; a comment marks where that helper replaces the lookup once 2026.9 is the minimum. The tests reach that registry through Home Assistant's own migration of a pre-2026.8 store, with either entry as the old device's primary. Topology also names the panel's current device and the entry that owns it, `panel_device_id` and `config_entry_id`. The card looked the id it holds up in the device list, which has no entry for an old id, so it lost the panel's config entry and the area its circuits fall back to. The card, rebuilt from span-card 849aefc, now takes both from topology, and it and the dashboard stop reading the device list's deprecated `config_entries`. The tests that still looked devices up with the unscoped call now ask within their entry. websocket-api.md records that an old id is still accepted, developer.md names the shared resolver, and the 2.1.2 changelog describes both fixes as a user meets them. --- CHANGELOG.md | 10 + custom_components/span_panel/adoption.py | 46 +++- custom_components/span_panel/binary_sensor.py | 2 + custom_components/span_panel/extension.py | 53 +++- .../frontend/dist/span-panel-card.js | 6 +- .../span_panel/frontend/dist/span-panel.js | 6 +- custom_components/span_panel/number.py | 1 + custom_components/span_panel/select.py | 1 + custom_components/span_panel/sensor.py | 2 + custom_components/span_panel/switch.py | 1 + custom_components/span_panel/websocket.py | 65 ++--- .../span_panel/websocket_adopted.py | 73 ++--- .../span_panel/websocket_panel.py | 102 +++++++ developer.md | 6 +- tests/test_adoption.py | 222 ++++++++++++--- tests/test_extension_entities.py | 64 ++++- tests/test_init_helpers.py | 4 +- tests/test_platform_entry_scope.py | 253 ++++++++++++++++++ tests/test_recreate_entity_ids.py | 8 +- tests/test_websocket.py | 55 ++-- tests/test_websocket_adopted.py | 48 +++- tests/test_websocket_panel.py | 238 ++++++++++++++++ websocket-api.md | 27 +- 23 files changed, 1080 insertions(+), 213 deletions(-) create mode 100644 custom_components/span_panel/websocket_panel.py create mode 100644 tests/test_platform_entry_scope.py create mode 100644 tests/test_websocket_panel.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f886600..bd92971c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ 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. + ## [2.1.1] - 8/2026 ### Added 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..ef12b9e7 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`