${[["cycles",this._t("tab.history",{},"Cycles")],["maintenance",this._t("tab.maintenance",{},"Maintenance")]].map(([s,a])=>`${a} `).join("")}
${t==="maintenance"?this._htmlMaintenance():this._htmlHistory()}`}_htmlHistory(){const t=this._cycles||[],e=this._refCycles||[],s=e.concat(t),a=this._canEdit(),r=this._selectMode&&a,i=this._cycleSel,{col:o,dir:n}=this._cycleSort,{text:l,status:c}=this._cycleFilter,p=this._mlById||{},_=new Set((this._feedbacks||[]).map(y=>y.cycle_id)),f=y=>p[y.id],u=y=>{const P=f(y);return!!(P&&P.ml_review&&P.ml_review.reviewed_at)},b=y=>{const P=f(y);return!!(P&&P.ml_review&&P.ml_review.golden)},v=y=>_.has(y.id),T=s.filter(v).length;let C=s;if(l){const y=l.toLowerCase();C=C.filter(P=>(P.profile_name||P.matched_profile||"").toLowerCase().includes(y))}c==="unlabelled"?C=C.filter(y=>!y.profile_name&&!y.matched_profile):c==="needs_review"?C=C.filter(v):c==="imported"?C=C.filter(y=>y.is_reference):c&&(C=C.filter(y=>(y.status||"completed")===c));const x={date:y=>y.start_time?new Date(y.start_time).getTime():0,confidence:y=>y.match_confidence,duration:y=>y.duration,energy:y=>y.energy_kwh!=null?y.energy_kwh:y.energy_wh!=null?y.energy_wh/1e3:null,cost:y=>y.cost!=null?y.cost:-1,status:y=>y.status||"completed",profile:y=>(y.profile_name||y.matched_profile||"\uFFFF").toLowerCase()};C=_e(C,x[o]||x.date,n);const $=y=>y==="completed"?"var(--success-color, #4caf50)":y==="interrupted"?"var(--error-color, #f44336)":y==="force_stopped"?"var(--warning-color, #ff9800)":"var(--secondary-text-color)",L=y=>{if(!y.is_reference)return"";const P=y.cycle_origin==="backfill",X=P?this._t("badge.backfilled_tip",{},"Detected in imported power history. Shapes program matching only, not counted in stats."):this._t("badge.imported_tip",{},"Imported from the community store. Used for matching only, not counted in stats.");return` ${this._t("msg.warmup_badge",{done:u,needed:f},`Still learning (${u}/${f} cycles)`)}`:"",T=t.is_imported?`\u{1F4E5} ${this._t("status.imported",{},"Imported")} `:"",C=t.is_imported?Number((this._profileMatcherCounts||{})[t.name]||0):null,x=C==null?"":`${C>0?this._t("badge.matcher_used",{n:C},`Matched ${C} of your cycles`):this._t("badge.matcher_unused",{},"Not matched to your cycles yet")} `,$=this._profileAdvisory(t.name,"unmatchable"),L=$?`\u26A0 ${this._t("badge.unmatchable",{},"can't be matched")} `:"",D=(this._profileTerminal||{})[t.name];let B="";if(D&&D.quiet_before_s>0&&D.seen_in>=2){const O=Math.max(1,Math.round(D.quiet_before_s/60)),K=Number(D.event_watts||0).toFixed(0),I=Math.round(D.event_seconds||0),R=this._tText("badge.quiet_tail_tip",{mins:O,secs:I,watts:K,seen:D.seen_in,measured:D.measured},`Near the end this program goes quiet for about ${O} min, then draws about ${K} W for ${I} s. Seen in ${D.seen_in} of ${D.measured} measured cycles.`);B=`${this._t("badge.quiet_tail",{mins:O},`~${O}m quiet tail`)} `}const U=this._profileAdvisory(t.name,"duration_outlier"),z=U?`\u26A0 ${this._t("badge.duration_outlier",{n:(U.message_params||{}).n||1},`${(U.message_params||{}).n||1} odd-length cycle(s)`)} `:"",M=[L,z,c,_,B,v,T,x].filter(Boolean).join(" "),A=Array.isArray(t.signature_curve)&&t.signature_curve.length>=3?` `:"";return`
${d(t.name)}
${A}
${M?`${M}
`:""}
${this._t("lbl.n_cycles",{n:t.cycle_count||0},"{n} cycles")} \xB7 ${e}${s}${a}${i}
`}_paintSparkline(t,e){if(!t||!Array.isArray(e)||e.length<3)return;const s=(getComputedStyle(this).getPropertyValue("--primary-color")||"#03a9f4").trim()||"#03a9f4",a=window.devicePixelRatio||1,r=t.getBoundingClientRect(),i=t.width=Math.max(1,Math.round((r.width||64)*a)),o=t.height=Math.max(1,Math.round((r.height||20)*a)),n=t.getContext("2d");n.clearRect(0,0,i,o);const l=Math.max(...e,1),c=2*a,p=f=>c+(e.length===1?0:f/(e.length-1)*(i-2*c)),_=f=>o-c-Math.max(0,f)/l*(o-2*c);n.beginPath(),e.forEach((f,u)=>{const b=p(u),v=_(f);u===0?n.moveTo(b,v):n.lineTo(b,v)}),n.strokeStyle=s,n.lineWidth=1.5*a,n.lineJoin="round",n.lineCap="round",n.stroke()}_drawProfileSparklines(){const t=this.shadowRoot;if(!t)return;const e=t.querySelectorAll("canvas[data-spark-prof]");if(!e.length)return;const s={};for(const a of this._profiles||[])s[a.name]=a;e.forEach(a=>{const r=s[a.dataset.sparkProf];this._paintSparkline(a,r&&r.signature_curve||[])})}_htmlProfiles(){const t=this._busy.has("rebuild-envelopes"),e=this._canEdit(),s={};this._profiles.forEach(v=>{s[v.name]=v});const a=this._profileGroups||{groups:[]},r=new Set;a.groups.forEach(v=>(v.members||[]).forEach(T=>r.add(T)));const i=a.groups.map(v=>{const T=(v.members||[]).map(D=>s[D]?this._profileCardHtml(s[D]):"").join(""),C=Math.round((v.cohesion!=null?v.cohesion:1)*100),x=v.cohesive?`${this._t("lbl.cohesion_good",{pct:C},"cohesion "+C+"%")} `:`${this._t("lbl.cohesion_low",{pct:C},"\u26A0 low cohesion "+C+"%")} `,$=v.cohesive?"":`${this._t("msg.group_not_cohesive",{},"These profiles aren't similar enough to group reliably, so matching treats them individually until you remove the outlier or split the group.")}
`;return`
${e?`\u{1F517} ${d(v.name)} `:`\u{1F517} ${d(v.name)} `}
${x}
${e?`${this._t("btn.manage",{},"Manage")} `:""}
${$}
${T}
`}).join(""),o=this._profiles.filter(v=>!r.has(v.name)),n=o.map(v=>this._profileCardHtml(v)).join(""),l=this._profiles.length===0&&!(this._cycles||[]).length&&!(this._refCycles||[]).length,c=e&&this._onlineEnabled()&&l?`
\u{1F4E5} ${this._t("msg.onboard_download",{},"New device? Adopt a ready-made setup (programs, reference cycles and phases) from another WashData user with the same appliance.")}
${this._t("btn.browse_community_setups",{},"Browse community setups")}
`:"",_=(e?((this._coverageGaps||{}).profile_suggestions||[]).slice(0,2):[]).map(v=>{const T=Math.round((v.avg_duration_s||0)/60),C=(v.cycle_ids||[])[0]||"";return`
\u{1F9E9} ${this._t("msg.coverage_gap_cluster",{count:v.count,min:T},`${v.count} recent unlabelled cycles of about ${T} min look like one program you have not created yet.`)}
${this._t("btn.create_profile_from_gap",{},"Create profile")}
`}).join(""),f=c+_+`
${this._t("tab.profiles",{},"Profiles")} (${this._profiles.length})
${this._t("msg.profiles_intro",{},"Click a profile for stats, phases and cleanup. Group near-identical programs (same shape/duration, different temperature or spin) so matching reliably picks between them.")}
${e?`
${this._t("btn.new_profile",{},"+ New Profile")}
${this._t("btn.new_group",{},"+ New Group")}
${t?' '+this._t("status.rebuilding",{},"Rebuilding\u2026"):this._t("btn.rebuild",{},"Rebuild Envelopes")}
`:""}
${i}
${this._profiles.length===0?`\u{1F4CA}
${this._t("msg.no_profiles_yet",{},"No profiles yet. Create one from a labelled cycle.")}
`:o.length?`${i?`${this._t("lbl.ungrouped",{},"Ungrouped")}
`:""}${n}
`:""}`,u=[["profiles",this._t("tab.subtab_profiles",{},"Profiles")],["phase-catalog",this._t("tab.subtab_phase_catalog",{},"Phase Catalog")]].map(([v,T])=>`${T} `).join(""),b=this._profilesError||this._profileGroupsError?`${this._t("msg.fetch_error",{},"Failed to load data.")} ${this._t("btn.retry",{},"Retry")}
`:"";return`
${u}
${this._profSubtab==="phase-catalog"?this._htmlPhases():b+f}
`}_htmlProfileGroupModal(t){const e=this._busy.has("pg-save"),s=this._profileEnvCache||{},a=_=>Q[Math.max(0,this._profiles.findIndex(f=>f.name===_))%Q.length],r=t.members||[],i=this._profiles.map(_=>{const f=r.includes(_.name),u=_.avg_duration?`~${Math.round(_.avg_duration/60)}m`:"",b=_.avg_energy!=null?` \xB7 ${gt(_.avg_energy)}`:"",v=f?` `:"";return` ${v}${d(_.name)} ${u}${b} `}).join(""),o=r.filter(_=>s[_]&&(s[_].avg||[]).length),n=o.length?`${o.map(_=>` ${d(_)} `).join("")}
`:"",l=o.length?`
${n}`:`${this._t("msg.group_preview_hint",{},"Tick 2+ members to preview and compare their power curves.")}
`,c=((this._profileGroups||{}).groups||[]).find(_=>_.name===t.orig),p=c&&c.cohesion!=null?`${c.cohesive?this._t("lbl.cohesion_good",{pct:Math.round(c.cohesion*100)},"cohesion "+Math.round(c.cohesion*100)+"%"):this._t("lbl.cohesion_low",{pct:Math.round(c.cohesion*100)},"\u26A0 low cohesion "+Math.round(c.cohesion*100)+"%")} `:"";return`${t.orig?this._t("modal.edit_group",{},"Edit profile group"):this._t("modal.new_group",{},"New profile group")}
${this._t("lbl.group_name",{},"Group name")} ${p}
${l}
${this._t("lbl.members",{},"Members")}${r.length?` (${r.length})`:""}
${i||`${this._t("msg.no_profiles_yet_short",{},"No profiles yet.")} `}
${this._t("msg.group_modal_help",{},"Group programs with the same shape that differ in temperature or spin. Matching scores the group as one, then picks the best-fitting member. Pick at least 2; the overlay shows how alike they are.")}
${this._t("btn.cancel",{},"Cancel")}
${t.orig?`${this._t("btn.delete_group",{},"Delete Group")} `:""}
${e?' '+this._t("status.saving",{},"Saving\u2026"):this._t("btn.save_group",{},"Save Group")}
`}_drawGroupCanvas(){const t=this._modal;if(!t||t.type!=="profile-group")return;const e=this._profileEnvCache||{},s=i=>Q[Math.max(0,this._profiles.findIndex(o=>o.name===i))%Q.length];let a=0;const r=(t.members||[]).filter(i=>e[i]&&(e[i].avg||[]).length).map(i=>{const o=e[i],n=o.avg[o.avg.length-1];return a=Math.max(a,o.target_duration||(n?n[0]:0)),{points:o.avg,stroke:s(i),width:2,alpha:.9,name:i}});r.length&&this._drawCurves("wd-pgroup-canvas",{series:r,xMax:a})}_settingsLevel(){return this._pref("settings_level","basic")==="advanced"?"advanced":"basic"}_settingFieldVisible(t){return t.internal&&!this._pref("show_internal",!1)&&!(this._internalReveal||new Set).has(t.key)?!1:this._settingsLevel()==="advanced"||!!t.basic}_computeInternalReveal(t){const e=new Set,s=this._optDefaults||{},a=this._conflictKeysFromOpts(),r=new Set((this._suggestions||[]).map(i=>i.key));for(const i of yt)for(const o of i.fields||(i.groups||[]).flatMap(n=>n.fields||[])){if(!o.internal)continue;const n=(this._opts||{})[o.key],l=s[o.key]!==void 0?s[o.key]:o.def;(n!=null&&n!==""&&String(n)!==String(l)||a.has(o.key)||r.has(o.key)||Object.prototype.hasOwnProperty.call(this._pendingSettings||{},o.key))&&e.add(o.key)}return e}_secHasBasicFields(t){return(t.fields||(t.groups||[]).flatMap(s=>s.fields||[])).some(s=>s.basic)}_htmlSettings(){const t=this._editedOpts();if(!Object.keys(t).length&&this._tabError==="settings")return`${this._t("msg.fetch_error",{},"Failed to load data.")} ${this._t("btn.retry",{},"Retry")}
`;if(!Object.keys(t).length)return`\u2699\uFE0F
${this._t("msg.loading_settings",{},"Loading settings\u2026")}
`;const e=this._suggestionsError?`${this._t("msg.fetch_error",{},"Failed to load data.")} ${this._t("btn.retry",{},"Retry")}
`:"",a=this._settingsLevel()==="basic";this._internalReveal=this._computeInternalReveal(t);const r=new Set((this._suggestions||[]).map(W=>W.key)),i=W=>(W.fields||(W.groups||[]).flatMap(Y=>Y.fields||[])).some(Y=>r.has(Y.key)),o=this._conflictKeysFromOpts(),n=W=>(W.fields||(W.groups||[]).flatMap(Y=>Y.fields||[])).some(Y=>o.has(Y.key)),l=this._opts&&this._opts.device_type||"",c=yt.filter(W=>!(W.id==="ml_training"||l&&W.notDeviceTypes&&W.notDeviceTypes.includes(l)||l&&W.onlyDeviceTypes&&!W.onlyDeviceTypes.includes(l)||a&&!this._secHasBasicFields(W))),p=(c.find(W=>W.id===this._settingsSec)||c[0]||{}).id,_=c.map(W=>{const J=i(W),Y=n(W);return`${d(this._t("section."+W.id+".label",{},W.label))}${Y?' ':J?' ':""} `}).join(""),f=`
${this._t("lbl.settings_basic",{},"Basic")}
${this._t("lbl.settings_advanced",{},"Advanced")}
`,u=a?`${this._t("msg.settings_basic_note",{},"Showing essential settings. Switch to Advanced for the full list.")}
`:"",b=a?"":`
${this._t("lbl.show_internal",{},"Show internal settings")} `,v=this._busy.has("save-settings"),T=o.size,C=T!==1?"s":"",x=T?`
\u26A0 ${this._t("conflict.settings_banner",{n:T},`Setting conflicts: ${T}. Check the highlighted sections and fix them before saving.`)}
${this._t("conflict.settings_banner_btn",{},"Go to first")}
`:"",$=this._suggestions.length,L=this._settingsSugOnly&&!this._settingsSearch,D=$?`${this._t("btn.apply_all",{},"Apply all")} `:"",B=$?`${this._t("btn.dismiss",{},"Dismiss")} `:"",U=$?L?`
\u{1F4A1} ${this._t("msg.showing_suggestions",{count:$},`Showing ${$} setting${$>1?"s":""} with suggestions.`)} ${this._t("msg.show_all_settings",{},"Show all settings")} .
${D}
`:`
\u{1F4A1} ${this._t("msg.tuning_suggestions_available",{count:$},`${$} tuning suggestion${$>1?"s":""} available from observed cycles. They appear beside the relevant fields.`)}
${this._t("btn.show_only",{},"Show only")}
${D}
${B}
`:"",z=this._busy.has("sug-analyze"),M=`${z?' '+this._t("status.analyzing",{},"Analyzing\u2026"):this._t("btn.run_analysis",{},"\u{1F50D} Run suggestion analysis")} `,A=this._settingsSearch||"",O=A.trim().toLowerCase(),K=` `,I=O?this._htmlSettingsSearch(t,O):L?this._htmlSettingsSugOnly(t):this._htmlSettingsSection(t),R=(this._lockedSuggestions||[]).length,G=R?`
\u{1F515} ${this._t("msg.n_suggestions_muted",{count:R},`${R} suggestion${R>1?"s":""} muted; the auto-tuner will not propose these.`)}
${this._t("btn.reset_muted",{},"Reset muted")}
`:"";return`
${e}
${this._t("tab.settings",{},"Settings")}
${M}
${x}${U}${G}${u}
${v?' '+this._t("status.saving",{},"Saving\u2026"):this._t("btn.save_settings",{},"Save Settings")}
${this._t("btn.revert_settings",{},"Revert changes")}
${this._t("btn.refresh",{},"Refresh")}
${this._t("msg.saving_triggers_reload",{},"Saving reloads the integration. Entities may briefly show as unavailable.")}
${this._htmlSettingsHistory()}
`}_htmlSettingsHistory(){const t=this._settingsChangelog||[];if(!t.length)return"";const e=this._settingsHistoryOpen,s=this._canEdit(),a=t.slice(0,100).map(r=>{const i=s?`${this._t("btn.revert",{},"Revert")} `:"";return`
${d(this._t("setting."+r.key+".label",{},r.key))}
${d(xt(r.old))} \u2192 ${d(xt(r.new))}
${et(r.timestamp)}
${i}
`}).join("");return`
${this._t("hdr.settings_history",{},"Settings history")} (${t.length})
${e?"\u25B2":"\u25BC"}
${e?`
${this._t("lbl.setting",{},"Setting")}
${this._t("lbl.change",{},"Change")}
${this._t("lbl.date",{},"Date")}
${a}
`:""}
`}_renderField(t,e){if(t.onlyDeviceType&&(e.device_type||"washing_machine")!==t.onlyDeviceType)return"";if(t.type==="storebrand"||t.type==="storemodel")return this._renderStorePicker(t,e);let s=e[t.key];if(s===void 0){const n=this._optDefaults;s=n&&n[t.key]!=null?n[t.key]:t.def}const a={};if(t.type==="devicetype")a.opts=this._deviceTypeOpts(s||e.device_type);else if(t.type==="device")a.opts=this._deviceOpts(s);else if(t.type==="select")a.opts=t.opts||[];else if(t.type==="entity"){const n=this._hass&&this._hass.states?this._hass.states:{},l=Array.isArray(t.domains)?t.domains:t.domain==="binary_sensor"?["binary_sensor","sensor"]:t.domain?[t.domain]:null,c=Object.keys(n).filter(p=>!l||l.some(_=>p.startsWith(_+"."))).filter(p=>!t.notPrice||Nt(p,e,this._conflictCtx())==null).sort().slice(0,500);this._entityListCache||(this._entityListCache={}),this._entityListCache[t.key]=c}else if(t.type==="entitylist"){const n=this._hass&&this._hass.states?this._hass.states:{},l=Object.keys(n).filter(c=>!t.domain||c.startsWith(t.domain+".")).sort();if(t.domain==="notify"&&this._hass&&this._hass.services&&this._hass.services.notify){const c=Object.keys(this._hass.services.notify).map(p=>`notify.${p}`);a.entities=[...new Set([...l,...c])].sort().slice(0,500)}else a.entities=l.slice(0,500);this._entityListCache||(this._entityListCache={}),this._entityListCache[t.key]=a.entities}else if(Array.isArray(t.suggestions)&&t.suggestions.length){const n=`wd-dl-${t.key}`;a.datalistId=n,a.datalist=`${t.suggestions.map(l=>``).join("")} `}const r=this._suggestions.find(n=>n.key===t.key);if(r){const n=Object.assign({},r.reason_params||{});r.exclusions&&r.exclusions.total&&(n.excl=this._exclNote(r.exclusions)),a.suggestion={suggested:r.suggested,current:r.current,reason:r.reason,reason_key:r.reason_key,reason_params:n}}a.useBtnLabel=this._t("btn.use",{},"Use"),a.t=this._t.bind(this),a.tText=this._tText.bind(this);const i=(this._settingsChangeByKey||{})[t.key];i&&(a.changed=this._tText("msg.setting_changed",{old:xt(i.old),new:xt(i.new),date:et(i.timestamp)},`Changed from ${xt(i.old)} to ${xt(i.new)} on ${et(i.timestamp)}`));const o=Object.assign({},t,{label:this._t("setting."+t.key+".label",{},t.label||""),doc:t.doc!=null?this._t("setting."+t.key+".doc",{},t.doc):t.doc});return Ge(o,s,a)}_renderStorePicker(t,e){const s=t.type==="storebrand",a=t.key,r=String(e[a]==null?"":e[a]),i=this._t("setting."+a+".label",{},t.label||""),o=this._t("setting."+a+".doc",{},t.doc||""),n=d(this._t("placeholder."+a,{},s?"e.g. Bosch":"e.g. WAT28660"));return this._onlineEnabled()?s?this._renderBrandPicker(a,r,i,o,n):this._renderModelPicker(a,e,r,i,o,n):``}_statusTag(t){if(t&&t.status==="pending"){const e=d(this._t("badge.awaiting_tip",{},"Awaiting community approval")),s=typeof t.confirmCount=="number"?this._t("badge.awaiting_n",{n:t.confirmCount},`Awaiting approval \xB7 ${t.confirmCount} confirmed`):this._t("badge.awaiting",{},"Awaiting approval");return`${s} `}return t&&t.status==="approved"?`${this._t("badge.approved",{},"Approved")} `:""}_renderBrandPicker(t,e,s,a,r){this._ensureCatalogEntry();const i=Array.isArray(this._catalog.brands)?this._catalog.brands:[];this._entityListCache=this._entityListCache||{},this._entityListCache[t]=i.map(l=>l.brand).filter(Boolean);const o=this._statusTag(this._catalogEntryFor(e,"brand")),n=this._catalog.brands===null?` ${this._t("msg.loading",{},"Loading\u2026")} `:"";return`${d(s)} ${a?ot(a):""}${o}${n}
`}_renderModelPicker(t,e,s,a,r,i){const o=String(e.store_brand||"");if(!o)return``;this._ensureCatalogEntry(),this._catalog.forBrand!==o&&(this._catalog.forBrand=o,this._catalog.devices=void 0);const n=Array.isArray(this._catalog.devices)?this._catalog.devices:[];this._entityListCache=this._entityListCache||{},this._entityListCache[t]=n.map(u=>u.model).filter(Boolean);const l=this._catalogEntryFor(s,"device"),c=this._statusTag(l),p=this._catalog.devices===null?` ${this._t("msg.loading",{},"Loading\u2026")} `:"";let _="";if(l){const u=[],b=je(l.manualUrl);b&&u.push(`${this._t("link.manual",{},"Manual \u2197")} `),(this._constants&&this._constants.storePrefs||{}).show_contributor!==!1&&u.push(this._t("store.contributed_by",{name:l.createdByName||this._tText("lbl.anonymous",{},"Anonymous")},"by {name}"));const v=!!(this._storeStatus&&this._storeStatus.connected);let T="";if(v){const C=[1,2,3,4,5].map(x=>`\u2605 `).join("");T=`${l.status==="pending"?this._t("btn.confirm_appliance",{},"Confirm this appliance"):this._t("btn.confirmed",{},"Confirmed")}
${C} `}else T=`${this._t("msg.connect_to_confirm",{},"Connect in the settings gear to confirm or rate.")} `;_=``}const f=this._onlineEnabled()&&this._storeDeviceDeclared()?`
${this._t("btn.share_device",{},"\u2B06 Share this device")}
${this._t("btn.download_device",{},"Download this setup")}
`:"";return`${d(a)} ${r?ot(r):""}${c}${p}
${_}${f}
`}_storeApplianceType(){return{washing_machine:"washer",washer:"washer",dryer:"dryer",dishwasher:"dishwasher",washer_dryer:"washer_dryer"}[this._opts.device_type]||""}_storeDeviceDeclared(){return!!((this._opts.store_brand||"").trim()&&(this._opts.store_model||"").trim())}_shareableByProgram(){const t=new Map;for(const s of this._shareableCycles||[]){const a=(s.profile_name||"").trim();a&&(t.has(a)||t.set(a,[]),t.get(a).push(s))}const e=Array.from(t.entries()).map(([s,a])=>({program:s,cycles:a})).sort((s,a)=>s.program.localeCompare(a.program));for(const s of this._shareAllPrograms||[])t.has(s)||e.push({program:s,cycles:[],noCycles:!0});return e}_editedOpts(){return Object.assign({},this._opts,this._pendingSettings)}_catalogEntryKey(){const t=this._editedOpts();return[(t.store_brand||"").trim().toLowerCase(),(t.store_model||"").trim().toLowerCase(),t.device_type||""].join("|")}_ensureCatalogEntry(){const t=this._catalogEntryKey(),e=this._catalogEntry;e&&e.key===t||(this._catalogEntry={key:t,brand:null,device:null,deviceId:null,loading:!0},this._loadCatalogEntry(t))}_catalogEntryFor(t,e){const s=this._catalogEntry;if(!s||s.key!==this._catalogEntryKey())return null;const a=e==="brand"?s.brand:s.device;if(!a)return null;const r=e==="brand"?a.brand:a.model;return String(r||"").toLowerCase()===String(t||"").trim().toLowerCase()?a:null}async _loadCatalogEntry(t){const e=this._devices[this._selIdx],s=this._editedOpts(),a=(s.store_brand||"").trim(),r=(s.store_model||"").trim();if(!e||!this._onlineEnabled()||!a||!r){this._catalogEntry&&this._catalogEntry.key===t&&(this._catalogEntry.loading=!1);return}let i=null;try{i=await this._ws({type:`${w}/store_get_catalog_entry`,entry_id:e.entry_id,brand:a,model:r,appliance_type:s.device_type||""})}catch{}!this._catalogEntry||this._catalogEntry.key!==t||(this._catalogEntry={key:t,loading:!1,brand:i&&i.brand||null,device:i&&i.device||null,deviceId:i&&i.device_id||null},this._isActiveEntry(e.entry_id)&&this._renderPreservingFormEdits())}_refreshComboAfterLoad(t,e,s=!0){const a=this.shadowRoot;for(const r of Array.isArray(t)?t:[t]){const i=a&&a.getElementById(r);if(i&&a.activeElement===i){if(s){i._wdSyntheticInput=!0;try{i.dispatchEvent(new Event("input",{bubbles:!0}))}finally{i._wdSyntheticInput=!1}}return}}this._isActiveEntry(e)&&this._render()}_ensureCatalogList(t,e,s=!1){if(this._onlineEnabled()){if(t==="store_brand"){this._ensureBrandCandidates(e);return}if(t==="store_search"){this._ensureBrandCandidates(e),s&&String(e||"").trim().length>=2&&this._ensureStoreTypeDevices();return}if(t==="store_model"){const a=String(this._editedOpts().store_brand||"").trim();if(!a)return;(this._catalog.forBrand!==a||this._catalog.devices===void 0)&&(this._catalog.forBrand=a,this._catalog.devices=null,this._loadCatalogDevices(a))}}}_ensureBrandCandidates(t){const e=String(t||"").trim().toLowerCase();if(this._catalog.brandsFull)return;if(!e){clearTimeout(this._brandSearchTimer),this._brandSearchTimer=null,this._catalog.brands===void 0&&(this._catalog.brands=null,this._loadCatalogBrands(""));return}(this._catalog.brandPrefixes||(this._catalog.brandPrefixes=[])).some(a=>e.startsWith(a))||(clearTimeout(this._brandSearchTimer),this._brandSearchTimer=setTimeout(()=>this._loadCatalogBrands(e),250))}_mergeBrandCandidates(t){const e=new Map((Array.isArray(this._catalog.brands)?this._catalog.brands:[]).map(s=>[String(s.id!=null?s.id:s.brand),s]));for(const s of t||[])e.set(String(s.id!=null?s.id:s.brand),s);this._catalog.brands=Array.from(e.values()).sort((s,a)=>String(s.brand||"").localeCompare(String(a.brand||"")))}async _loadCatalogBrands(t=""){this._entityListCache=this._entityListCache||{};let e=!1;const s=this._devices[this._selIdx];if(!s||!this._onlineEnabled()){this._catalog.brands=[],this._entityListCache.store_brand=[];return}if(!(t&&this._catalog.brandsFull)){this._catalog.brands===void 0&&(this._catalog.brands=null);try{const a=await this._ws({type:`${w}/store_list_brands`,entry_id:s.entry_id,query:t||null,include_pending:!this._catalog.approvedOnly}),r=a&&a.items||[];t?(this._mergeBrandCandidates(r),(this._catalog.brandPrefixes||(this._catalog.brandPrefixes=[])).push(t)):(this._catalog.brands=r,this._catalog.brandsFull=!0)}catch{Array.isArray(this._catalog.brands)||(this._catalog.brands=[]),e=!0}this._entityListCache.store_brand=(this._catalog.brands||[]).map(a=>a.brand).filter(Boolean),this._syncStoreSearchCandidates(),this._refreshComboAfterLoad(["wd-store-brand","wd-store-q"],s.entry_id,!e)}}_syncStoreSearchCandidates(){this._entityListCache=this._entityListCache||{};const t=new Map,e=[];for(const a of this._catalog.typeDevices||[]){const r=String(a&&a.model||"").trim();if(!r)continue;const i=a.brand?`${r} \xB7 ${a.brand}`:r;t.has(i)||(t.set(i,a),e.push(i))}this._catalog.modelIndex=t;const s=(this._catalog.brands||[]).map(a=>a.brand).filter(Boolean);this._entityListCache.store_search=s.concat(e)}_dropModelCandidates(){this._catalog.typeDevices=void 0,this._catalog.typeDevicesFor=null,this._catalog.modelIndex=new Map,this._entityListCache&&delete this._entityListCache.store_search}_ensureStoreTypeDevices(){const t=this._devices[this._selIdx];if(!t||!this._onlineEnabled())return;const e=this._storeApplianceType();this._catalog.typeDevicesFor===e&&this._catalog.typeDevices!==void 0||(this._catalog.typeDevicesFor=e,this._catalog.typeDevices=null,this._ws({type:`${w}/store_search_devices`,entry_id:t.entry_id,query:null,appliance_type:e,include_pending:!0}).then(s=>{this._catalog.typeDevicesFor===e&&(this._catalog.typeDevices=s&&s.items||[])}).catch(()=>{this._catalog.typeDevicesFor===e&&(this._catalog.typeDevices=[])}).finally(()=>{this._syncStoreSearchCandidates(),this._refreshComboAfterLoad(["wd-store-q"],t.entry_id,!!(this._catalog.typeDevices||[]).length)}))}async _loadCatalogDevices(t){this._entityListCache=this._entityListCache||{};const e=this._devices[this._selIdx];if(!e||!this._onlineEnabled()||!t){this._catalog.devices=[],this._entityListCache.store_model=[];return}try{const s=await this._ws({type:`${w}/store_search_devices`,entry_id:e.entry_id,query:t,appliance_type:this._storeApplianceType(),include_pending:!this._catalog.approvedOnly});this._catalog.forBrand===t&&(this._catalog.devices=s&&s.items||[])}catch{this._catalog.forBrand===t&&(this._catalog.devices=[])}this._entityListCache.store_model=(this._catalog.devices||[]).map(s=>s.model).filter(Boolean),this._refreshComboAfterLoad("wd-store-model",e.entry_id)}async _loadShareProfiles(){const t=this._devices[this._selIdx],e=this._modal;if(!t||!e||e.type!=="store-share")return;const s=(this._opts.store_brand||"").trim(),a=(this._opts.store_model||"").trim();if(!s||!a){e.profiles=[],this._modal===e&&this._render();return}try{const r=await this._ws({type:`${w}/store_get_device_profiles`,entry_id:t.entry_id,brand:s,model:a,appliance_type:this._opts.device_type||""});if(this._modal!==e)return;e.profiles=r&&r.items||[],e.deviceId=r&&r.device_id||null,e.storeUnreachable=!!(r&&r.error==="store_unreachable")}catch{this._modal===e&&(e.profiles=[])}this._modal===e&&this._render()}_htmlAutomations(){const t=this._deviceAutomations||[],e=t.length?t.map(r=>`\u{1F517} ${d(r.name)}${r.enabled?"":` ${this._t("lbl.automation_off",{},"(off)")} `} \xD7 `).join(""):`${this._autoLoading?this._t("msg.loading",{},"Loading\u2026"):this._t("hdr.no_automations",{},"No automations reference this device yet.")} `,s=Array.isArray(this._opts.notify_actions)?this._opts.notify_actions:[],a=s.length?`
${this._t("msg.legacy_actions_title",{count:s.length},`${s.length} legacy custom action${s.length>1?"s":""} still running`)}
${this._t("msg.old_actions_warning",{},"Configured with the old actions editor (now removed). They still fire on cycle events but can no longer be edited here. Convert them into a normal automation, or remove them.")}
${this._t("btn.convert_to_automation",{},"Convert to automation")}
${this._t("btn.remove",{},"Remove")}
`:"";return`
${this._t("hdr.automations",{},"Automations")}
${this._t("msg.automations_intro",{start:vt("ha_washdata_cycle_started"),end:vt("ha_washdata_cycle_ended")},"WashData fires {start} / {end} events and exposes entities, so notifications and actions are best built as normal Home Assistant automations. Automations that use this device appear below.")}
${a}
${e}
${this._t("btn.new_automation",{},"\uFF0B New Automation")}
${this._t("btn.from_template",{},"From template \u25BE")}
`}async _loadDeviceAutomations(t){this._deviceAutomations=[];const e=this._hass;if(!(!e||!e.callWS))try{let s=null;const a=e.devices||{};for(const n of Object.values(a))if((n.config_entries||[]).includes(t)){s=n.id;break}const r=s?await e.callWS({type:"search/related",item_type:"device",item_id:s}):await e.callWS({type:"search/related",item_type:"config_entry",item_id:t});if(!this._isActiveEntry(t))return;const i=r&&r.automation||[],o=e.states||{};this._deviceAutomations=i.map(n=>{const l=o[n]&&o[n].attributes||{};return{entity_id:n,id:l.id,name:l.friendly_name||n,enabled:o[n]?o[n].state==="on":!0}}).filter(n=>n.id)}catch{this._isActiveEntry(t)&&(this._deviceAutomations=[])}}_navigate(t){try{history.pushState(null,"",t),this.dispatchEvent(new CustomEvent("location-changed",{bubbles:!0,composed:!0,detail:{replace:!1}}))}catch{try{window.location.assign(t)}catch{}}}async _newAutomationFromEvent(t){const e=this._devices[this._selIdx];if(!e)return;const s=this._hass,a=t==="started"?"ha_washdata_cycle_started":"ha_washdata_cycle_ended",r=e.title||"WashData",i={alias:t==="started"?this._tText("msg.automation_alias_started",{device:r},"{device}: cycle started"):this._tText("msg.automation_alias_finished",{device:r},"{device}: cycle finished"),description:t==="started"?this._tText("msg.automation_desc_started",{device:r},"Runs when a {device} cycle starts. Add your actions (notify, lights, ...)."):this._tText("msg.automation_desc_finished",{device:r},"Runs when a {device} cycle finishes. Add your actions (notify, lights, ...)."),mode:"single",trigger:[{platform:"event",event_type:a,event_data:{entry_id:e.entry_id}}],condition:[],action:[]},o="washdata_"+Date.now().toString(36);try{s&&s.callApi?(await s.callApi("POST","config/automation/config/"+o,i),this._navigate("/config/automation/edit/"+o)):this._navigate("/config/automation/edit/new")}catch(n){this._showToast(this._tText("msg.toast_automation_failed",{error:n.message||n},"Could not create automation: "+(n.message||n)),"error")}}async _convertLegacyActions(){const t=this._devices[this._selIdx],e=this._hass,s=Array.isArray(this._opts.notify_actions)?this._opts.notify_actions:[];if(!t||!s.length)return;const a={alias:`${t.title||"WashData"}: migrated custom actions`,description:"Migrated from WashData legacy custom actions (which ran on cycle start, finish and live). Trim the triggers as needed. Note: old {device}/{duration}-style placeholders are NOT templated here - replace them with Jinja templates such as {{ trigger.event.data.device_name }}.",mode:"single",trigger:[{platform:"event",event_type:"ha_washdata_cycle_started",event_data:{entry_id:t.entry_id}},{platform:"event",event_type:"ha_washdata_cycle_ended",event_data:{entry_id:t.entry_id}}],condition:[],action:s},r="washdata_"+Date.now().toString(36);if(!e||!e.callApi){this._showToast(this._t("msg.toast_no_automation",{},"Cannot create automation here"),"error");return}let i=!1;try{await e.callApi("POST","config/automation/config/"+r,a),i=!0,await this._ws({type:`${w}/set_options`,entry_id:t.entry_id,options:{notify_actions:[]}}),this._isActiveEntry(t.entry_id)&&(this._opts={...this._opts,notify_actions:[]}),this._showToast(this._t("msg.toast_automation_migrated",{},"Actions migrated to an automation; opening editor")),this._navigate("/config/automation/edit/"+r)}catch(o){const n=o.message||o;if(!i){this._showToast(this._tText("msg.toast_convert_failed",{error:n},"Convert failed: "+n),"error");return}let l=null;try{const c=await this._ws({type:`${w}/get_options`,entry_id:t.entry_id}),p=c&&c.options||{},_=Array.isArray(p.notify_actions)?p.notify_actions:[];l=_.length>0,this._isActiveEntry(t.entry_id)&&(this._opts={...this._opts,notify_actions:_})}catch{l=null}if(l===!1){this._showToast(this._t("msg.toast_automation_migrated",{},"Actions migrated to an automation; opening editor")),this._navigate("/config/automation/edit/"+r);return}if(l===!0){try{await e.callApi("DELETE","config/automation/config/"+r),this._showToast(this._tText("msg.toast_convert_rolled_back",{error:n},"Migration failed and was rolled back (no automation left behind): "+n),"error")}catch{this._showToast(this._t("msg.toast_convert_orphan",{},"The automation was created, but clearing the old actions failed. Do not retry: remove the legacy actions manually to avoid a duplicate automation."),"error")}return}this._showToast(this._t("msg.toast_convert_orphan",{},"The automation was created, but clearing the old actions failed. Do not retry: remove the legacy actions manually to avoid a duplicate automation."),"error")}}_htmlSettingsSection(t){const e=this._opts&&this._opts.device_type||"",s=this._settingsLevel()==="basic",a=l=>!(l.id==="ml_training"||e&&l.notDeviceTypes&&l.notDeviceTypes.includes(e)||e&&l.onlyDeviceTypes&&!l.onlyDeviceTypes.includes(e)||s&&!this._secHasBasicFields(l)),r=yt.find(l=>l.id===this._settingsSec&&a(l))||yt.find(l=>a(l))||yt[0],i=r.intro||this._t("section."+r.id+".intro",{},"")?`${d(this._t("section."+r.id+".intro",{},r.intro||""))}
`:"",o="";if(r.id==="notifications"){const l=`${this._t("msg.notify_services_hint",{entity:vt("notify.<name>"),vars:vt(""+d(Pe)+"")},"Use {entity} service IDs (comma-separated for multiple). Template variables: {vars}.")}
`,c=r.groups.map(_=>{const f=(_.fields||[]).filter(u=>this._settingFieldVisible(u)).map(u=>this._renderField(u,t)).filter(Boolean).join("");return f?`${d(this._t("setting_group."+ge(_.sub)+".label",{},_.sub))}
${f}
`:""}).join("");return`${s?"":this._htmlAutomations()}${l}${c}`}if(r.groups){const l=r.groups.map(c=>{const p=c.sub?`${d(this._t("setting_group."+ge(c.sub)+".label",{},c.sub))}
`:"",_=(c.fields||[]).filter(f=>this._settingFieldVisible(f)).map(f=>this._renderField(f,t)).filter(Boolean).join("");return _?`${p}${_}
`:""}).join("");return`${i}${o}${l}`}const n=(r.fields||[]).filter(l=>this._settingFieldVisible(l)).map(l=>this._renderField(l,t)).filter(Boolean).join("");return`${i}${o}${n}
`}_htmlSettingsSearch(t,e){const s=this._opts&&this._opts.device_type||"",a=yt.filter(n=>!(n.id==="ml_training"||s&&n.notDeviceTypes&&n.notDeviceTypes.includes(s)||s&&n.onlyDeviceTypes&&!n.onlyDeviceTypes.includes(s))),r=n=>{const l=n.key?`${this._tText("setting."+n.key+".label",{},"")} ${n.doc!=null?this._tText("setting."+n.key+".doc",{},""):""}`:"";return`${n.label||""} ${n.key||""} ${n.doc||""} ${n.hint||""} ${l}`.toLowerCase().includes(e)};let i="",o=0;for(const n of a){const c=(n.fields||(n.groups||[]).flatMap(_=>_.fields||[])).filter(r);if(!c.length)continue;const p=c.map(_=>this._renderField(_,t)).filter(Boolean).join("");p&&(o+=c.length,i+=`${d(this._t("section."+n.id+".label",{},n.label))}
${p}
`)}return o?i:`${this._t("msg.no_settings_match",{q:e},'No settings match "{q}"')}
`}_sugCountForDevice(t){return t?Array.isArray(t.suggestion_keys)?t.suggestion_keys.length:t.suggestions_count||0:0}_htmlSettingsSugOnly(t){const e=new Set((this._suggestions||[]).map(i=>i.key));if(!e.size)return`${this._t("msg.no_suggestions",{},"No active suggestions.")}
`;const s=this._opts&&this._opts.device_type||"",a=yt.filter(i=>!(i.id==="ml_training"||s&&i.notDeviceTypes&&i.notDeviceTypes.includes(s)||s&&i.onlyDeviceTypes&&!i.onlyDeviceTypes.includes(s)));let r="";for(const i of a){const n=(i.fields||(i.groups||[]).flatMap(c=>c.fields||[])).filter(c=>e.has(c.key));if(!n.length)continue;const l=n.map(c=>this._renderField(c,t)).filter(Boolean).join("");l&&(r+=`${d(this._t("section."+i.id+".label",{},i.label))}
${l}
`)}return r||`${this._t("msg.no_suggestions",{},"No active suggestions.")}
`}_htmlMlTab(){const t=this._opts;if(!Object.keys(t).length)return`\u{1F916}
${this._t("msg.loading",{},"Loading\u2026")}
`;const e=this._mlTrainingStatus,s=this._devices[this._selIdx],a=s&&s.entry_id,r=yt.find(n=>n.id==="ml_training"),i=r?(r.fields||[]).map(n=>this._renderField(n,t)).filter(Boolean).join(""):"",o=this._busy.has("save-settings");return`
${this._t("hdr.ml_smart_learning",{},"Smart Learning")}
${this._t("msg.ml_intro",{},"WashData ships with smart models that work out of the box.")}
${this._htmlMlStatusSection(e,a)}
${this._t("hdr.ml_settings_card",{},"Settings")}
${this._t("msg.ml_settings_intro",{},"Two independent switches: one applies the models while a cycle runs, the other lets WashData fine-tune them to your machine over time.")}
${o?' '+this._t("status.saving",{},"Saving\u2026"):this._t("btn.save",{},"Save")}
${this._t("msg.saving_triggers_reload",{},"Saving reloads the integration. Entities may briefly show as unavailable.")}
${this._htmlMlLearnedSection(e)}
`}_htmlMlStatusSection(t,e){const s=e&&this._busy.has("ml-train-now:"+e)||t&&t.running,a=this._canEdit()?`${s?` ${this._t("status.training",{},"Training\u2026")}`:this._t("btn.train_now",{},"Train now")} `:"";if(!t)return`${this._t("hdr.status",{},"Status")}
${this._t("msg.loading",{},"Loading\u2026")}
`;const r=Object.keys(t.on_device_models||{}).length,i=r?`${this._t("ml.personalized",{},"\u25CF Personalized to this machine")} ${this._t("lbl.models_fine_tuned",{count:r},"(fine-tuned models: "+r+")")} `:`${this._t("ml.builtin_models",{},"\u25CF Using built-in models")} `,o=t.cycle_count||0,n=t.min_cycles||0,l=o>=n,c=n>0?Math.min(100,Math.round(o/n*100)):100,p=l?"var(--success-color,#4caf50)":"var(--warning-color,#ff9800)",_=Math.max(0,n-o),f=l?this._t("msg.enough_data",{current:o,min:n},`Enough data to learn from (${o}/${n} cycles).`):this._t("msg.collecting_data",{need:_,current:o,min:n},`Collecting data. Cycles still needed before fine-tuning can start: ${_} (${o}/${n}).`),u=``,b=t.last_trained?et(t.last_trained):this._tText("ml.never",{},"never"),v=s?` ${this._t("status.fine_tuning",{},"fine-tuning now\u2026")} `:t.enabled?this._t("lbl.auto_fine_tune_on",{hour:String(t.hour).padStart(2,"0")},`auto fine-tune on (around ${String(t.hour).padStart(2,"0")}:00)`):this._t("lbl.auto_fine_tune_off",{},"auto fine-tune off");return`
${this._t("hdr.status",{},"Status")}
${a}
${i}
${f}
${u}
${this._t("lbl.last_checked",{},"Last checked:")} ${d(b)} \xB7 ${v}
`}_htmlMlLearnedSection(t){if(!t)return"";const e=t.on_device_models||{},s=Object.keys(e),a=this._busy.has("ml-revert-models"),r=t.last_run||{};let i;if(s.length){const o=s.map(l=>{const c=e[l]||{},p=c.trained_at?et(c.trained_at):this._tText("ml.trained_unknown",{},"at an unknown time");return`
${d(c.label_key?this._t(c.label_key,{},c.label||l):c.label||l)}${this._mlTrendBadge(c.trend)}
${d(c.blurb_key?this._t(c.blurb_key,{},c.blurb||""):c.blurb||"")} \xB7 ${this._t("ml.fine_tuned_at",{when:p},"fine-tuned {when}")}
${this._mlLastRunLine(r[l])}
${this._mlQualityChip(c)}
`}).join(""),n=this._canEdit()?`${a?' '+this._t("status.resetting",{},"Resetting\u2026"):this._t("btn.reset_to_builtin",{},"Reset to built-in models")} `:"";i=`${o}
${n}`}else{const o=Object.keys(r).map(n=>this._mlLastRunLine(r[n])).filter(Boolean).join("");i=`${this._t("msg.no_fine_tuned",{},"Nothing fine-tuned yet - WashData is using its built-in models.")}
${o}`}return`
${this._t("hdr.ml_learned",{},"What WashData has learned")}
${this._t("msg.ml_learned_intro",{},"Models fine-tuned to this machine.")}
${i}
`}_mlQualityChip(t){let e=0,s="",a=t.metric_key?this._tText(t.metric_key,t.metric_params||{},t.metric||""):t.metric||"";if(t.model_mae!=null&&t.naive_mae!=null&&t.naive_mae>0){const i=Math.max(0,(t.naive_mae-t.model_mae)/t.naive_mae);e=Math.min(1,i)*100,s=i>=.5?this._t("ml.fit_strong",{},"Strong"):i>=.2?this._t("ml.fit_good",{},"Good"):this._t("ml.fit_slight",{},"Slight"),a=this._tText("ml.better_than_baseline",{pct:(i*100).toFixed(0),metric:a},`${(i*100).toFixed(0)}% better than the baseline estimate (${a})`)}else return"";Number.isInteger(t.held_out_cycles)&&(a+=" \xB7 "+this._tText("ml.held_out_count",{count:t.held_out_cycles},`measured on ${t.held_out_cycles} held-out cycles`));const r=e>=70?"var(--success-color,#4caf50)":e>=40?"var(--warning-color,#ff9800)":"var(--secondary-text-color)";return`
${s} ${this._t("ml.fit_word",{},"fit")}
`}_mlTrendBadge(t){if(!t)return"";const s={improving:[this._t("badge.improving",{},"\u2197 improving"),"var(--success-color,#4caf50)",this._t("ml.trend_improving_tip",{},"This model's fit has improved across recent re-checks.")],declining:[this._t("badge.declining",{},"\u2198 declining"),"var(--warning-color,#ff9800)",this._t("ml.trend_declining_tip",{},"This model's fit has slipped across recent re-checks \u2013 reviewing more cycles may help it re-learn.")],steady:[this._t("badge.steady",{},"\u2192 steady"),"var(--secondary-text-color)",this._t("ml.trend_steady_tip",{},"This model's fit has held roughly steady across recent re-checks.")]}[t];return s?` ${s[0]} `:""}_mlSkipReason(t){const e={insufficient_rows:"Not enough usable cycles yet: {count} labelled, clean cycles so far.",holdout_too_small:"Too few cycles to test a new model fairly: {held_out} could be set aside for testing, {min} are needed.",not_better_than_naive:"The new model was not clearly better than the simple estimate (error {model} vs {naive}).",not_better_than_incumbent:"The new model was not better than the one in use (error {model} vs {incumbent}), so the current one was kept.",fit_failed:"Training failed: {error}",unknown:"Nothing new was learnt."},s=Object.prototype.hasOwnProperty.call(e,t&&t.reason_code)?t.reason_code:"unknown",a=t&&t.reason_params||{},r=s==="insufficient_rows"?{count:Number(a.cycles)||0}:{...a};return[s,this._tText(`ml.skip_reason.${s}`,r,e[s])]}_mlLastRunLine(t){if(!t||t.promoted!==!1)return"";const[e,s]=this._mlSkipReason(t),a=t.ts?et(t.ts):this._tText("ml.trained_unknown",{},"at an unknown time");return`${this._t("ml.last_run_not_promoted",{when:a,reason:s},"Last check ({when}): {reason}")}
`}_pgOverrideFields(){return[["start_threshold_w","Start Threshold","W","Minimum watts to count as started","detection"],["stop_threshold_w","Stop Threshold","W","Below this, machine counts as off","detection"],["off_delay","Off Delay","s","Seconds of low power before cycle ends","timing"],["min_off_gap","Min Off Gap","s","Gap required to separate two cycles","timing"],["completion_min_seconds","Min Cycle Duration","s","Shortest run that counts as a real cycle","timing"],["start_duration_threshold","Start Duration","s","Seconds above threshold to confirm start","timing"],["interrupted_min_seconds","Interrupted Min","s","Short cycles flagged as interrupted","advanced"],["anti_wrinkle_enabled","Enable Anti-Wrinkle Detection","","Absorb the tumble pulses after the main phase instead of reading them as new cycles","advanced","bool"],["anti_wrinkle_max_power","Max Anti-Wrinkle Power","W","A pulse above this ends anti-wrinkle and opens a new cycle","advanced"],["anti_wrinkle_max_duration","Max Anti-Wrinkle Duration","s","A pulse longer than this ends anti-wrinkle and opens a new cycle","advanced"],["anti_wrinkle_exit_power","Anti-Wrinkle Exit Power","W","Quiet level between tumble pulses; only takes effect above the Stop Threshold","advanced"],["anti_wrinkle_idle_timeout","Max Pulse Gap","s","Quiet time allowed between two tumble pulses before anti-wrinkle ends","advanced"],["dishwasher_end_spike_quiet_release","Passive-Dry Quiet Release","s","Dishwasher: quiet seconds after expected duration before the end-of-cycle drain wait is released","advanced"],["smart_termination_duration_ratio","Smart Termination Ratio","","Fraction of the program's average duration a cycle must reach before Smart Termination may end it early; lower it if run times vary","advanced"],["anti_crease_finalize_ratio","Anti-Crease Finalize Ratio","","Fraction of the program's average duration a cycle must reach before the anti-crease tail may finish it; lower it on dryers, keep washers at the default","advanced"],["curve_preroll_seconds","Curve Pre-roll","s","Seconds of readings from aborted start attempts carried into the front of a cycle's curve; 0 = off","timing"],["profile_match_min_duration_ratio","Min Duration Ratio","","Minimum cycle length as a fraction of the profile's duration; default 0.1 (10%)","matching"],["profile_match_max_duration_ratio","Max Duration Ratio","","Maximum cycle length as a fraction of the profile's duration; default 1.8 (180%)","matching"]]}_pgFieldVal(t,e){const s=e||{};if(s[t]!==void 0)return s[t];const a=this._pgEffective||{};if(a[t]!==void 0&&a[t]!==null)return a[t];const r=this._opts||{};if(r[t]!==void 0&&r[t]!==null)return r[t];const i=wt[t]||{};if(i.def!==void 0)return i.def;const o=this._constants&&this._constants.pgMatchDefaults||{};return o[t]!==void 0?o[t]:te[t]!==void 0?te[t]:""}async _pgFetchSettings(t,e=!0){try{const s=await this._ws({type:`${w}/get_playground_settings`,entry_id:t,include_suggestions:e});if(!this._isActiveEntry(t))return;this._pgEffective=s.effective||{},this._pgPublishable=Array.isArray(s.publishable)?s.publishable:null,this._pgPresets=Array.isArray(s.presets)?s.presets:[],this._pgPresetLimit=s.preset_limit||0,this._pgPresetSel&&!this._pgPresets.some(a=>a.name===this._pgPresetSel)&&(this._pgPresetSel=""),e&&this._pgApplySuggestions(s)}catch(s){this._pgIsUnknownCmd(s)&&(this._pgNeedsRestart=!0)}}_pgApplySuggestions(t){this._pgSuggClassic=t.classic_suggestions&&typeof t.classic_suggestions=="object"?t.classic_suggestions:{}}async _pgFetchSuggestions(t){try{const e=await this._ws({type:`${w}/get_playground_settings`,entry_id:t,include_suggestions:!0});if(!this._isActiveEntry(t))return;this._pgApplySuggestions(e),this._tab==="playground"&&this._render()}catch{}}_pgCurrentValues(){const t={};for(const[e]of this._pgOverrideFields()){const s=this._pgFieldVal(e,{});s!==""&&s!==null&&s!==void 0&&(t[e]=s)}this._pgThreshStart!=null&&(t.start_threshold_w=this._pgThreshStart),this._pgThreshStop!=null&&(t.stop_threshold_w=this._pgThreshStop);for(const[e,s]of Object.entries(this._pgParamOverrides||{}))t[e]=s;return t}_pgStagedVal(t){return t==="start_threshold_w"?this._pgThreshStart??void 0:t==="stop_threshold_w"?this._pgThreshStop??void 0:this._pgParamOverrides[t]}_pgSetStaged(t,e){t==="start_threshold_w"?this._pgThreshStart=e:t==="stop_threshold_w"?this._pgThreshStop=e:this._pgParamOverrides[t]=e}_pgClearStaged(t){t==="start_threshold_w"?this._pgThreshStart=null:t==="stop_threshold_w"?this._pgThreshStop=null:delete this._pgParamOverrides[t]}_pgChangedKeys(){const t=this._pgEffective||{},e=[];for(const[s]of this._pgOverrideFields()){const a=this._pgStagedVal(s);if(a==null)continue;const r=t[s];r!=null&&this._pgSameVal(a,r)||e.push(s)}return e}_pgSameVal(t,e){if(typeof t=="boolean"||typeof e=="boolean")return!!t==!!e;const s=parseFloat(t),a=parseFloat(e);return!isNaN(s)&&!isNaN(a)?Math.abs(s-a)<1e-9:String(t)===String(e)}_pgIsPublishable(t){return Array.isArray(this._pgPublishable)?this._pgPublishable.includes(t):!!wt[t]}_htmlPgControlPanel(){const t=this._pgChangedKeys(),e=this._canEdit(),s=!!this._pgEffective,a=t.filter(b=>this._pgIsPublishable(b)),r=s?t.length?`${this._t("msg.pg_n_changed",{n:t.length},t.length+" changed vs live settings")} `:`\u2713 ${this._t("msg.pg_matches_live",{},"Matches live settings")} `:`${this._t("msg.pg_live_unavailable",{},"Live settings unavailable, showing defaults.")} `,i=this._pgPresets||[],o=`${d(this._tText("lbl.pg_preset_none",{},"Select a preset\u2026"))} `+i.map(b=>`${d(b.name)} `).join(""),n=!!this._pgPresetSel,l=this._pgPresetAtLimit((this._pgPresetName||"").trim()),c=`
${o}
${this._t("btn.pg_preset_load",{},"Load preset")}
${e?`\u{1F5D1} `:""}
`,p=e?`
${this._t("btn.pg_preset_save",{},"Save as preset")}
${this._t("msg.pg_preset_limit",{n:this._pgPresetLimit},"Preset limit reached ({n})")}
`:"",_=e&&a.length?`${this._t("btn.pg_publish_all",{n:a.length},"Publish "+a.length+" to integration")} `:"",f=Object.keys(this._pgSuggClassic||{}).length,u=f>0?`\u2193 ${this._t("btn.pg_load_suggested",{n:f},"Load suggested ("+f+")")} `:"";return`
${this._t("hdr.pg_settings_source",{},"Settings source")}
${r}
${this._t("msg.pg_ctrl_intro",{},"Values start from this device's live integration settings. Edits stay in the Playground until you publish them.")}
\u27F3 ${this._t("btn.pg_load_live",{},"Load live settings")}
${u}
${_}
${c}
${p}
`}_pgPresetAtLimit(t){const e=this._pgPresets||[];return this._pgPresetLimit>0&&e.length>=this._pgPresetLimit&&!e.some(s=>s.name===t)}_pgApplyPresetValues(t){this._pgThreshStart=null,this._pgThreshStop=null,this._pgParamOverrides={};const e=this._pgEffective||{};for(const[s,a]of Object.entries(t||{})){if(a==null)continue;const r=e[s];r!=null&&this._pgSameVal(a,r)||this._pgSetStaged(s,a)}}_pgLoadPreset(){const t=(this._pgPresets||[]).find(e=>e.name===this._pgPresetSel);t&&(this._pgApplyPresetValues(t.values),this._showToast(this._tText("toast.pg_preset_loaded",{name:t.name},`Preset "${t.name}" loaded`)),this._render(),requestAnimationFrame(()=>this._pgDrawCanvas()))}async _pgSavePreset(){const t=this._devices[this._selIdx],e=(this._pgPresetName||"").trim();!t||!this._canEdit()||!e||(this._pgPresets||[]).some(s=>s.name===e)&&!confirm(this._tText("msg.pg_preset_overwrite",{name:e},`Overwrite the preset "${e}"?`))||(await this._busyRun("pg-preset-save",async()=>{try{const s=await this._ws({type:`${w}/save_playground_preset`,entry_id:t.entry_id,name:e,values:this._pgCurrentValues()});if(!this._isActiveEntry(t.entry_id))return;this._pgPresets=Array.isArray(s.presets)?s.presets:this._pgPresets,this._pgPresetSel=e,this._pgPresetName="",this._showToast(this._tText("toast.pg_preset_saved",{name:e},`Preset "${e}" saved`))}catch(s){this._showToast(this._tText("msg.toast_save_failed",{error:s.message||s},"Save failed: "+(s.message||s)),"error")}}),this._render())}async _pgDeletePreset(){const t=this._devices[this._selIdx],e=this._pgPresetSel;!t||!this._canEdit()||!e||confirm(this._tText("msg.pg_preset_delete_confirm",{name:e},`Delete the preset "${e}"?`))&&(await this._busyRun("pg-preset-delete",async()=>{try{const s=await this._ws({type:`${w}/delete_playground_preset`,entry_id:t.entry_id,name:e});if(!this._isActiveEntry(t.entry_id))return;this._pgPresets=Array.isArray(s.presets)?s.presets:(this._pgPresets||[]).filter(a=>a.name!==e),this._pgPresetSel=""}catch(s){this._showToast(this._tText("msg.toast_error",{error:s.message||s},"Error: "+(s.message||s)),"error")}}),this._render())}async _pgLoadLive(){const t=this._devices[this._selIdx];if(!t)return;const e=this._pgChangedKeys();e.length&&!confirm(this._tText("msg.pg_load_live_confirm",{n:e.length},`Discard ${e.length} Playground edit(s) and reload the integration's current settings?`))||(await this._busyRun("pg-load-live",async()=>{this._pgThreshStart=null,this._pgThreshStop=null,this._pgParamOverrides={},await this._pgFetchSettings(t.entry_id)}),this._render(),requestAnimationFrame(()=>this._pgDrawCanvas()))}_pgLoadSuggested(){const t=this._pgSuggClassic||{},e=this._pgEffective||{};let s=0;for(const[a,r]of Object.entries(t)){if(r==null)continue;const i=e[a];i!=null&&this._pgSameVal(r,i)||(this._pgSetStaged(a,r),s++)}s===0?this._showToast(this._t("msg.pg_sugg_none",{},"All suggestions already match the current settings")):this._showToast(this._tText("toast.pg_sugg_loaded",{n:s},`Staged ${s} suggested value(s) - run the Playground to compare`)),this._render(),requestAnimationFrame(()=>this._pgDrawCanvas())}async _pgPublishOne(t){const e=this._devices[this._selIdx];if(!e||!this._canEdit()||!t||!this._pgIsPublishable(t))return;const s=this._pgStagedVal(t);if(s==null)return;const a=this._t("setting."+t+".label",{},t);confirm(this._tText("msg.pg_publish_one_confirm",{label:a,value:s},`Save ${a} = ${s} to this device's settings?`))&&(await this._busyRun("pg-publish-"+t,async()=>{try{if(await this._ws({type:`${w}/set_options`,entry_id:e.entry_id,options:{[t]:s}}),!this._isActiveEntry(e.entry_id))return;this._opts={...this._opts,[t]:s},this._pgEffective&&(this._pgEffective={...this._pgEffective,[t]:s}),this._pgClearStaged(t),this._showToast(this._t("toast.settings_saved",{},"Settings saved; integration reloading"))}catch(r){this._showToast(this._tText("msg.toast_save_failed",{error:r.message||r},"Save failed: "+(r.message||r)),"error")}}),this._render())}_htmlPlayground(){if(!this._devices[this._selIdx])return`${this._t("msg.no_device_selected",{},"No device selected.")}
`;const e=this._cycles||[],s=this._profiles||[],a=e.map(u=>{const b=u.profile_name||u.matched_profile||this._t("lbl.unlabelled",{},"Unlabelled"),v=u.duration?` \xB7 ${Math.round(u.duration/60)} ${this._tText("lbl.timer_min",{},"min")}`:"",T=u.start_time?` \xB7 ${et(u.start_time)}`:"";return`${d(b+v+T)} `}).join(""),r=`${d(this._t("lbl.auto_detect",{},"Auto-detect"))} `+s.map(u=>`${d(u.name)} `).join(""),i=this._pgLoading,o=`
${this._t("lbl.cycle",{},"Cycle")} ${a||'\u2014 '}
${this._t("lbl.pg_compare_profile",{},"Compare with profile")} ${r}
\u25B6 ${this._t("btn.run",{},"Run")}
${i?`\u2715 ${this._t("btn.cancel",{},"Cancel")} `:""}
`,n=i?`
${this._t("msg.pg_simulating",{},"Simulating cycle\u2026")}
`:"",l=!this._pgPowerPts&&!i?`
〜
${this._t("msg.pg_canvas_empty3",{},"Pick a cycle above and press Run to simulate it. Then hover to read values, scroll or pinch to zoom, and drag to pan.")}
`:"",c=`${n} ${l}
`,p=this._htmlPgStrip(),_=this._pgNeedsRestart?`\u26A0 ${this._t("msg.pg_restart_note",{},"Restart Home Assistant to enable simulation tools.")}
`:"",f=`${o}${c}${p}
${this._htmlPgParamRows()}
${this._htmlPgAlerts()}${this._htmlPgAnalysis()}
`;return`
${this._t("hdr.playground",{},"Playground")}
${this._t("msg.playground_intro",{},"Try settings on your real cycle data. Nothing here changes your live settings until you apply it.")}
${_}
${f}
${this._htmlPgDrawer()}
`}_htmlPgDrawer(){const t=this._pgAnalysisTab||"history",s=`
${[["history",this._t("lbl.pg_mode_history",{},"Test on history")],["sweep",this._t("lbl.pg_mode_optimize",{},"Optimize")]].map(([r,i])=>`${d(i)} `).join("")}
`,a=t==="sweep"?this._htmlPgSweepMode():this._htmlPgHistoryMode();return`
${this._t("hdr.pg_across_cycles",{},"Across your cycles")}
${s}
${a}
`}_htmlPgParamRows(){const t=this._pgOverrideFields(),e=new Set(["start_threshold_w","stop_threshold_w"]),s={detection:"#2a78d6",timing:"#1baf7a",advanced:"#eda100",matching:"#a05cd6"},a={detection:this._t("lbl.pg_group_detection",{},"Detection triggers"),timing:this._t("lbl.pg_group_timing",{},"Timing rules"),advanced:this._t("lbl.pg_group_advanced",{},"Edge cases"),matching:this._t("lbl.pg_group_matching",{},"Program matching")};let r="";const i=t.map(([o,n,l,c,p,_])=>{const f=this._t("setting."+o+".label",{},n),u=this._pgFieldVal(o,{});let b;o==="start_threshold_w"?b=this._pgThreshStart??u:o==="stop_threshold_w"?b=this._pgThreshStop??u:b=this._pgParamOverrides[o]??u;const v=_==="bool",T=e.has(o),C=l||"",x=s[p]||"#2a78d6",$=a[p]||"";let L="";p&&p!==r&&(L=``,r=p);const D=this._pgStagedVal(o),B=D!=null&&!(u!==""&&u!==null&&u!==void 0&&this._pgSameVal(D,u)),U=this._canEdit()&&B&&this._pgIsPublishable(o)?`\u2191 `:' ';return`${L}`}).join("");return`
${this._t("hdr.pg_detection_params",{},"Detection settings")}
${this._htmlPgControlPanel()}
${i}
${this._t("btn.reset",{},"Reset")}
${this._pgDetailBusy?' ':""}
`}_htmlPgAlerts(){const t=this._pgDetail;if(!t)return"";const e=t.outcome||{},s={error:"var(--error-color,#f44336)",warn:"var(--warning-color,#ff9800)",info:"var(--info-color,#2196f3)"},a=Array.isArray(t.alerts)?t.alerts:[],r=a.length?a.map(f=>`
${d(this._pgAlertLabel(f.code))}
${d(f.detail_key?this._tText(f.detail_key,f.detail_params||{},f.detail||""):f.detail||"")}
`).join(""):`\u2713 ${this._t("msg.pg_no_alerts",{},"No issues detected in this run.")}
`,i=e.termination_reason?String(e.termination_reason):"\u2014",o=e.final_duration_s?Math.round(e.final_duration_s/60)+" min":"\u2014",n=e.projected_energy_wh!=null?e.projected_energy_wh>=1e3?it(e.projected_energy_wh/1e3,2)+" kWh":it(Math.round(e.projected_energy_wh),0)+" Wh":"\u2014",l=(t.events||[]).some(f=>String(f.type||"").startsWith("notify_")),c=(f,u)=>``,_=this._pgDetailSig!=null&&this._pgDetailSig!==this._pgOverrideSig(this._pgCurrentOverride())?`
\u26A0 ${this._t("msg.pg_outcome_stale",{},"Settings changed since this simulation ran. It still shows the old result.")}
\u25B6 ${this._t("btn.pg_rerun",{},"Update simulation")}
`:"";return`
${this._t("hdr.pg_outcome",{},"Simulation outcome")}
${_}
${c(this._t("lbl.pg_ended",{},"Ended"),i)}
${c(this._t("lbl.duration",{},"Duration"),o)}
${c(this._t("lbl.pg_proj_energy",{},"Proj. energy"),n)}
${e.label_reason?c(this._t("lbl.pg_autolabel",{},"Auto-label"),this._pgLabelVerdict(e).short):""}
${r}
${l?`
\u{1F514} ${this._t("msg.pg_notify_partial",{},"Markers show start, almost-done, finish, milestone and quiet-hours notifications only. Live updates, unload reminders and overrun alerts are not simulated.")}
`:""}
`}_pgLabelVerdict(t){if(t.would_label&&t.label_profile)return{ok:!0,short:String(t.label_profile),tip:this._tText("msg.pg_label_yes",{name:t.label_profile},`Would be labelled "${t.label_profile}" at cycle end`)};const s={no_winner:this._tText("pg_label_reason.no_winner",{},"No program fit"),below_floor:this._tText("pg_label_reason.below_floor",{},"Below the learning confidence"),ambiguous:this._tText("pg_label_reason.ambiguous",{},"Flagged ambiguous"),margin:this._tText("pg_label_reason.margin",{},"Too close to the runner-up"),unknown_profile:this._tText("pg_label_reason.unknown_profile",{},"Program no longer exists")}[t.label_reason]||this._tText("pg_label_reason.not_evaluated",{},"Not evaluated");return{ok:!1,short:this._tText("lbl.pg_label_review",{},"Asks for review"),tip:this._tText("msg.pg_label_no",{reason:s},`Would ask for review: ${s}`)}}_pgAlertLabel(t){return{overrun:this._t("lbl.pg_alert_overrun",{},"Overrun"),underrun:this._t("lbl.pg_alert_underrun",{},"Underrun"),did_not_finish:this._t("lbl.pg_alert_did_not_finish",{},"Did not finish"),false_end:this._t("lbl.pg_alert_false_end",{},"Split into multiple cycles"),unmatched:this._t("lbl.pg_alert_unmatched",{},"Unmatched"),ambiguous:this._t("lbl.pg_alert_ambiguous",{},"Ambiguous match"),energy_anomaly:this._t("lbl.pg_alert_energy",{},"Energy anomaly"),timeout_end:this._t("lbl.pg_alert_timeout_end",{},"Ended by timeout, not prediction"),would_run_indefinitely:this._t("lbl.pg_alert_indefinite",{},"Would run indefinitely")}[t]||t}_htmlPgHistoryMode(){const t=this._busy.has("pg-history"),e=this._pgHistory,s=Object.keys(this._pgParamOverrides||{}).length>0||this._pgThreshStart!=null||this._pgThreshStop!=null,a=`
${this._t("lbl.last",{},"Last")}
${this._t("lbl.cycles_lc",{},"cycles")}
\u25B6 ${this._t("btn.run",{},"Run")}
${s?`\u2699 ${this._t("msg.pg_override_active",{},"Using your edited settings vs. current")} `:""}
`,r=t?this._htmlPgBatchBar():"",i=`${this._t("msg.pg_history_intro2",{},"Replay recent cycles through the real detector and matcher with the settings above. Click a row to load it in the graph; change a setting to compare before and after.")}
`;if(!e||!Array.isArray(e.rows))return`${i}${a}${this._htmlPgRecentRuns("pg_history")}${r}${t?"":`${this._t("msg.pg_history_empty",{},"Press Run to replay your cycles.")}
`}`;let o="";if(e.diff){const u=(b,v,T)=>`${b} ${d(T)} `;o=`
${u((e.diff.newly_correct||[]).length,"var(--success-color,#4caf50)",this._t("lbl.pg_newly_correct",{},"newly correct"))}
${u((e.diff.regressed||[]).length,"var(--error-color,#f44336)",this._t("lbl.pg_regressed",{},"regressed"))}
${u((e.diff.end_timing_changed||[]).length,"var(--warning-color,#ff9800)",this._t("lbl.pg_end_timing_changed",{},"end-timing changed"))}
`}const n=e.summary||{},l=`${this._t("msg.pg_history_summary",{detected:n.detected,correct:n.match_correct,total:n.cycles},`${n.detected}/${n.cycles} detected \xB7 ${n.match_correct} matched correctly`)}
`,c=u=>(this._cycles||[]).find(b=>b.id===u),p={};(e.baseline_rows||[]).forEach(u=>{p[u.cycle_id]=u});const _=e.rows.map(u=>{const b=c(u.cycle_id),v=b&&b.start_time?et(b.start_time):u.start_time?et(u.start_time):(u.cycle_id||"").slice(0,8),T=u.match_correct===!0?"\u2713":u.match_correct===!1?"\u2717":"\u2014",C=u.match_correct===!0?"var(--success-color,#4caf50)":u.match_correct===!1?"var(--error-color,#f44336)":"var(--secondary-text-color)",x=u.matched_profile||this._t("lbl.unlabelled",{},"Unlabelled"),$=u.duration_s?Math.round(u.duration_s/60)+"m":"\u2014",L=u.overrun_ratio!=null?` (${Math.round(u.overrun_ratio*100)}%)`:"",D=u.label_reason?this._pgLabelVerdict(u):null,B=D?` ${D.ok?"\u{1F3F7}":"?"} `:"",U=u.alerts&&u.alerts.length?` \u26A0 `:"",z=p[u.cycle_id];let M="";return z&&(z.match_correct!==!0&&u.match_correct===!0?M=`\u25B2 ${this._t("lbl.pg_fixed",{},"fixed")} `:z.match_correct===!0&&u.match_correct!==!0?M=`\u25BC ${this._t("lbl.pg_broke",{},"broke")} `:String(z.termination_reason)!==String(u.termination_reason)&&(M=`${d(String(z.termination_reason||"\u2014"))}\u2192${d(String(u.termination_reason||"\u2014"))} `)),`
${d(v)}
${T} ${d(x)}${B}
${d(String(u.termination_reason||"\u2014"))}
${$}${L}${U}
${e.diff?`${M} `:""}
`}).join(""),f=`
${this._t("lbl.cycle",{},"Cycle")}
${this._t("lbl.match",{},"Match")}
${this._t("lbl.pg_ended",{},"Ended")}
${this._t("lbl.duration",{},"Duration")}
${e.diff?`${this._t("lbl.pg_vs_current",{},"vs current")} `:""}
${_}
`;return`${i}${a}${this._htmlPgRecentRuns("pg_history")}${o}${l}${this._htmlPgInSampleNote()}${f}`}_htmlPgInSampleNote(){return`${this._t("msg.pg_in_sample_note",{},"These cycles helped build the profiles they are matched against, so results are optimistic: expect slightly fewer correct matches on new cycles.")}
`}_htmlPgBatchBar(){const t=this._pgBatchProgress;return t?`
${t.done} /${t.total}
\u2715 ${this._t("btn.cancel",{},"Cancel")}
`:""}_pgUpdateBatchBar(t,e){const s=this.shadowRoot;if(!s)return;const a=s.getElementById("wd-pg-batch-fill"),r=s.getElementById("wd-pg-batch-count"),i=e?Math.round(100*t/e):0;a&&(a.style.width=i+"%"),r&&(r.textContent=String(t))}async _pgRunHistory(){const t=this._devices[this._selIdx];if(!t)return;if(!(this._cycles||[]).length){this._showToast(this._t("msg.no_cycles_selected",{},"No cycles available."),"error");return}const e=Math.max(1,Math.min(St,this._pgSimCycles||20)),s=this._pgCurrentOverride();this._pgBatchProgress={done:0,total:e},this._busy.add("pg-history"),this._render();try{const a=await this._ws({type:`${w}/start_playground_history`,entry_id:t.entry_id,count:e,settings_override:s});if(this._pgHistoryTaskId=a&&a.task_id,this._pgNeedsRestart=!1,!this._pgHistoryTaskId)throw new Error("no task id");this._addProvisionalTask(this._pgHistoryTaskId,"pg_history",t.entry_id,e),this._tasksSubscribed||this._pgPollTask(this._pgHistoryTaskId)}catch(a){this._busy.delete("pg-history"),this._pgBatchProgress=null,this._pgRunFailed(a),this._render()}}_pgRunFailed(t){if(this._pgIsUnknownCmd(t)){this._pgNeedsRestart=!0;return}if((t&&(t.code||t.error&&t.error.code)||"")==="task_busy"){this._showToast(this._t("msg.pg_task_busy",{},"A Test-on-history or Optimize run is already in progress for this device. Wait for it or cancel it."),"error");return}this._showToast(this._tText("msg.toast_error",{error:t.message||t},"Error: "+(t.message||t)),"error")}_pgSweepObjectives(){return[["match_accuracy",this._t("lbl.pg_obj_match",{},"Match accuracy"),!1],["end_timing_accuracy",this._t("lbl.pg_obj_endtiming",{},"End-timing accuracy"),!1],["false_end_rate",this._t("lbl.pg_obj_falseend",{},"False-end rate"),!0],["median_overrun",this._t("lbl.pg_obj_overrun",{},"Duration off-target"),!0],["ambiguity_rate",this._t("lbl.pg_obj_ambiguity",{},"Ambiguity rate"),!0],["end_lag",this._t("lbl.pg_obj_endlag",{},"End delay (median)"),!0],["early_end_rate",this._t("lbl.pg_obj_early",{},"Early-end rate"),!0],["split_rate",this._t("lbl.pg_obj_split",{},"Split rate"),!0]]}_htmlPgSweepMode(){const t=this._busy.has("pg-sweep"),s=this._pgOverrideFields().filter(([,,,,,n])=>n!=="bool").map(([n,l])=>`${d(this._t("setting."+n+".label",{},l))} `).join(""),a=this._pgSweepObjectives().map(([n,l])=>`${d(l)} `).join(""),r=`${this._t("msg.pg_sweep_intro2",{},"Find the setting that best meets an objective across your recent cycles. Nothing changes until you apply it.")}
`,i=`
${this._t("lbl.pg_sweep_param",{},"Parameter")} ${s}
${this._t("lbl.pg_objective",{},"Objective")} ${a}
${this._t("lbl.from",{},"From")}
${this._t("lbl.to",{},"To")}
${this._t("lbl.pg_steps",{},"Steps")}
${this._t("lbl.pg_sweep_cycles",{},"Recent cycles")}
\u25B6 ${this._t("btn.run",{},"Run")}
`,o=t?this._htmlPgBatchBar():"";return`${r}${i}${this._htmlPgRecentRuns("pg_sweep")}${o}${this._htmlPgSweepResult()}`}_htmlPgSweepResult(){const t=this._pgSweepNew;if(!t||!Array.isArray(t.points)||!t.points.length)return"";const e=this._pgSweepObjectives().find(x=>x[0]===t.objective),s=t.lower_is_better!=null?!!t.lower_is_better:e?e[2]:!1,a=t.points.filter(x=>x.metric!=null).map(x=>x.metric);if(!a.length)return`${this._t("msg.pg_sweep_no_metric",{},"Not enough data to score this objective.")}
`;const r=Math.min(...a),i=Math.max(...a),o=t.best_value!==void 0,n=s?r:i,l=o?t.best_value:(t.points.find(x=>x.metric===n)||{}).value,c=o?t.best_metric:n,p=!!t.keep_current,_=(x,$)=>x!=null&&$!=null&&Math.abs(Number(x)-Number($))<1e-6,f=x=>x==null?"-":t.objective==="median_overrun"?Math.round(x*100)+"% off":t.objective==="end_lag"?`${it(x/60,1)} ${this._tText("lbl.timer_min",{},"min")}`:Math.round(x*100)+"%",u=t.points.map(x=>{const $=i>r&&x.metric!=null?(x.metric-r)/(i-r):x.metric!=null?1:0,L=!p&&_(x.value,l),D=_(x.value,t.current_value),B=L?"var(--success-color,#4caf50)":"var(--primary-color)",U=x.guarded?` \u26A0 `:"";return`
${d(String(x.value))}${D?" \u25C0":""}${U}
${f(x.metric)}
`}).join(""),b=!p&&l!=null&&!_(l,t.current_value),v=this._canEdit()&&b?`${this._t("btn.pg_apply_best",{},"Apply best")} `:"",T=t.cycles?` \xB7 ${this._t("msg.pg_sweep_n_cycles",{count:t.cycles},`over ${t.cycles} cycles`)}`:"";return`${p?`${this._t("msg.pg_sweep_keep_current",{value:String(t.current_value??"-"),metric:f(c)},`Keep the current value (${t.current_value??"-"}, ${f(c)}): no value did better by at least one cycle.`)} `:`${this._t("lbl.pg_best_value",{},"Best value found")}: ${d(String(l))} \xB7 ${d(f(c))}`}${T} \xB7 \u25C0 ${this._t("lbl.pg_current_value",{},"Current value:")}
${u}
${this._htmlPgInSampleNote()}${v}
`}async _pgRunSweep2(){const t=this._devices[this._selIdx];if(!t)return;const e=parseFloat(this._pgSweepFrom),s=parseFloat(this._pgSweepTo);if(isNaN(e)||isNaN(s)||e===s){this._showToast(this._t("msg.toast_range_required",{},"Set a valid From/To range."),"error");return}const a=Math.max(2,Math.min(12,this._pgSweepSteps||5)),r=Array.from({length:a},(c,p)=>+(e+(s-e)*p/(a-1)).toFixed(3)),i=this._pgSweepParam||"off_delay",o=this._pgSweepObjective||"match_accuracy",n=Math.max(1,Math.min(St,this._pgSimCycles||20)),l={type:`${w}/start_playground_sweep`,entry_id:t.entry_id,param:i,values:r,objective:o,count:n};this._pgBatchProgress={done:0,total:r.length+1},this._busy.add("pg-sweep"),this._render();try{const c=await this._ws(l);if(this._pgSweepTaskId=c&&c.task_id,this._pgNeedsRestart=!1,!this._pgSweepTaskId)throw new Error("no task id");this._addProvisionalTask(this._pgSweepTaskId,"pg_sweep",t.entry_id,r.length+1),this._tasksSubscribed||this._pgPollTask(this._pgSweepTaskId)}catch(c){this._busy.delete("pg-sweep"),this._pgBatchProgress=null,this._pgRunFailed(c),this._render()}}async _pgApplyToSettings(){const t=this._devices[this._selIdx];if(!t||!this._canEdit())return;const e={};for(const r of this._pgChangedKeys())this._pgIsPublishable(r)&&(e[r]=this._pgStagedVal(r));const s=Object.keys(e);if(!s.length)return;const a=s.map(r=>this._t("setting."+r+".label",{},r)).join(", ");confirm(this._tText("msg.pg_apply_settings_confirm",{n:s.length,list:a},`Save these ${s.length} setting(s) to this device? ${a}`))&&(await this._busyRun("pg-apply-settings",async()=>{try{if(await this._ws({type:`${w}/set_options`,entry_id:t.entry_id,options:e}),!this._isActiveEntry(t.entry_id))return;this._opts={...this._opts,...e},this._pgEffective&&(this._pgEffective={...this._pgEffective,...e});for(const r of s)this._pgClearStaged(r);this._showToast(this._t("toast.settings_saved",{},"Settings saved; integration reloading"))}catch(r){this._showToast(this._tText("msg.toast_save_failed",{error:r.message||r},"Save failed: "+(r.message||r)),"error")}}),this._render())}async _pgApplySweepValue(t){const e=this._devices[this._selIdx];if(!e||!this._canEdit()||t==null)return;const s=this._pgSweepParam,a=this._t("setting."+s+".label",{},s);confirm(this._tText("msg.pg_apply_confirm",{label:a,value:t},"Apply best value: "+a+" = "+t+"?"))&&await this._busyRun("pg-sweep-apply",async()=>{try{await this._ws({type:`${w}/set_options`,entry_id:e.entry_id,options:{[s]:+t}}),this._opts={...this._opts,[s]:+t},s==="start_threshold_w"?this._pgThreshStart=+t:s==="stop_threshold_w"?this._pgThreshStop=+t:this._pgParamOverrides[s]=+t,this._showToast(this._t("toast.settings_saved",{},"Settings saved; integration reloading")),this._render()}catch(r){this._showToast(this._tText("msg.toast_save_failed",{error:r.message||r},"Save failed: "+(r.message||r)),"error")}})}_htmlPgStrip(){return`
${this._t("lbl.pg_idle",{},"Idle")}
${this._t("lbl.power",{},"Power")} \u2014
\u2014%
${this._t("lbl.pg_time_left_model",{},"Time left (model)")} \u2014
${this._t("lbl.energy",{},"Energy")} \u2014
${this._t("lbl.match",{},"Match")} \u2014
${this._t("lbl.phase",{},"Phase")} \u2014
`}_htmlPgAnalysis(){const t=this._pgDetail&&this._pgDetail.outcome,e=t&&t.matched_profile||this._pgProfileName||(this._cycles||[]).find(o=>o.id===this._pgCycleId)?.profile_name||"",s=t&&t.confidence;if(!t)return`${this._t("msg.pg_analysis_hint2",{},"Pick a cycle and press Run to load match analysis.")}
`;let a="\u2014",r="var(--secondary-text-color)";s!=null&&(s>=.7?(a="\u2705 "+this._t("lbl.pg_strong_match",{},"Strong match"),r="var(--success-color, #4caf50)"):s>=.4?(a="\u26A0 "+this._t("lbl.pg_weak_match",{},"Weak match"),r="var(--warning-color, #ff9800)"):(a="\u274C "+this._t("lbl.pg_poor_match",{},"Poor match"),r="var(--error-color, #f44336)"));let i=`${a}
`;if(e&&s!=null){const o=Math.round(Math.max(0,Math.min(1,s))*100);i+=`${d(this._t("lbl.pg_match_confidence",{},"Match confidence"))}
`,i+=``}return`${i}
`}async _pgFetchEnvelope(t,e){try{const s=await this._ws({type:`${w}/get_profile_envelope`,entry_id:t,profile_name:e});this._pgEnvData=s.envelope||null}catch{this._pgEnvData=null}}async _pgLoad(){const t=this._devices[this._selIdx];if(!t||this._pgLoading)return;const e=this._pgCycleId||this._cycles?.[0]?.id||"";if(!e)return;this._pgCycleId=e,this._pgLoading=!0,this._pgDetailBusy=!1,this._pgView=null,this._pgHoverT=null,this._pgPowerPts=null,this._pgEnvData=null,this._pgDetail=null;const s=++this._pgLoadSeq;this._render();try{const a=await this._ws({type:`${w}/get_cycle_power_data`,entry_id:t.entry_id,cycle_id:e}),r=a.samples||[],i=[];for(const l of r){if(!Array.isArray(l)||l.length<2)continue;const c=+l[0],p=+l[1];!isNaN(c)&&!isNaN(p)&&i.push({t:c,w:p})}if(this._pgPowerPts=i.length?i:null,typeof a.full_duration_s=="number"&&a.full_duration_s>0){const l=(this._cycles||[]).find(c=>c.id===e);l&&(l._pg_duration=a.full_duration_s)}const o=this._pgProfileName||(this._cycles||[]).find(l=>l.id===e)?.profile_name||"";if(o&&await this._pgFetchEnvelope(t.entry_id,o),s!==this._pgLoadSeq)return;await this._pgLoadDetail(t.entry_id,e,s);const n=this._pgDetail&&this._pgDetail.outcome&&this._pgDetail.outcome.matched_profile;!o&&n&&s===this._pgLoadSeq&&await this._pgFetchEnvelope(t.entry_id,n)}catch(a){this._showToast(this._tText("msg.toast_error",{error:a.message||a},"Error: "+(a.message||a)),"error")}s===this._pgLoadSeq&&(this._pgLoading=!1,this._render(),requestAnimationFrame(()=>{this._pgDrawCanvas(),this._pgUpdateStripAt(null)}),this._pgNeedsRestart&&this._tab==="playground"?(this._pgRestartRetries||0)<5&&!this._pgRestartRetryTimer&&(this._pgRestartRetries=(this._pgRestartRetries||0)+1,this._pgRestartRetryTimer=setTimeout(()=>{this._pgRestartRetryTimer=null,this._pgNeedsRestart&&this._tab==="playground"&&this._pgLoad()},3e3)):this._pgRestartRetries=0)}_pgCancelRun(){this._pgLoadSeq++,this._pgLoading=!1,this._pgDetailBusy=!1,this._pgCancelDetailTask(),this._render()}_pgCancelDetailTask(){const t=this._pgDetailTaskId;this._pgDetailTaskId=null,t&&this._ws({type:`${w}/cancel_task`,task_id:t}).catch(()=>{})}_pgCurrentOverride(){const t={...this._pgParamOverrides};return this._pgThreshStart!=null&&(t.start_threshold_w=this._pgThreshStart),this._pgThreshStop!=null&&(t.stop_threshold_w=this._pgThreshStop),t}_pgOverrideSig(t){return JSON.stringify(Object.keys(t).sort().map(e=>[e,t[e]]))}_pgSelectCycle(t){t&&(this._pgCycleId=t,this._pgProfileName="",this._pgView=null,this._pgHoverT=null,this._pgLoading=!1,this._pgLoad(),requestAnimationFrame(()=>{const e=this.shadowRoot&&this.shadowRoot.getElementById("wd-pg-canvas");e&&typeof e.scrollIntoView=="function"&&e.scrollIntoView({behavior:"smooth",block:"center"})}))}async _pgLoadDetail(t,e,s){const a=this._pgCurrentOverride(),r=this._pgOverrideSig(a);this._pgCancelDetailTask();try{const i=await this._ws({type:`${w}/start_playground_cycle_detail`,entry_id:t,cycle_id:e,settings_override:a}),o=i&&i.task_id;if(!o)throw new Error("no task id");this._pgDetailTaskId=o,this._addProvisionalTask(o,"pg_detail",t,0);const n=await new Promise(l=>{this._taskCallbacks[o]=async p=>{if(p.state==="done"||p.state==="cancelled")try{const _=await this._ws({type:`${w}/get_task_result`,task_id:p.id});l(_&&_.result)}catch{l(null)}else l(null)};const c=this._tasks[o];c&&c.state!=="running"?this._settleTaskCallback(c):this._tasksSubscribed||this._pollTaskGeneric(o)});if(this._pgDetailTaskId===o&&(this._pgDetailTaskId=null),!this._isActiveEntry(t)||s!=null&&s!==this._pgLoadSeq)return;this._pgDetail=n&&!n.error?n:null,this._pgDetailSig=this._pgDetail?r:null,this._pgNeedsRestart=!1}catch(i){if(s!=null&&s!==this._pgLoadSeq)return;this._pgIsUnknownCmd(i)&&(this._pgNeedsRestart=!0),this._pgDetail=null,this._pgDetailSig=null}}_pgRerunDetail(){const t=this._devices[this._selIdx];!t||!this._pgCycleId||this._pgLoading||(clearTimeout(this._pgDetailDebounceTimer),this._pgDetailDebounceTimer=setTimeout(async()=>{const e=++this._pgLoadSeq;this._pgDetailBusy=!0,this._render(),await this._pgLoadDetail(t.entry_id,this._pgCycleId,e),e===this._pgLoadSeq&&(this._pgDetailBusy=!1,this._render(),requestAnimationFrame(()=>this._pgDrawCanvas()))},220))}_pgMapState(t,e){return e?"stalled":t==="running"||t==="paused"?"running":t==="ending"?"ending":t==="starting"?"detecting":t==="anti_wrinkle"?"anti_wrinkle":"idle"}_pgSeriesAt(t){const e=this._pgDetail&&this._pgDetail.series;if(!e||!e.length)return null;let s=e[0];for(const a of e)if(a.t<=t)s=a;else break;return s}_pgStateSegsFromSeries(t){const e=this._pgDetail&&this._pgDetail.series;if(!e||!e.length)return[];const s=[];let a=null;for(const r of e){const i=this._pgMapState(r.state,r.stalled);!a||a.state!==i?(a={start:r.t,end:r.t,state:i},s.push(a)):a.end=r.t}return s.length&&(s[s.length-1].end=t),s}_pgDrawCanvas(){if(this._tab!=="playground")return;const t=this.shadowRoot,e=t&&t.getElementById("wd-pg-canvas");if(!e)return;const s=this._pgPowerPts,a=e.getBoundingClientRect(),r=window.devicePixelRatio||1,i=Math.max(1,Math.round(a.width*r)),o=Math.max(1,Math.round((a.height||280)*r));(e.width!==i||e.height!==o)&&(e.width=i,e.height=o);const n=e.getContext("2d"),l=getComputedStyle(this),c=(l.getPropertyValue("--primary-color")||"#03a9f4").trim(),p=(l.getPropertyValue("--divider-color")||"rgba(127,127,127,.2)").trim(),_=(l.getPropertyValue("--secondary-text-color")||"#888").trim(),f=(l.getPropertyValue("--secondary-background-color")||"#1a1a1a").trim();n.clearRect(0,0,i,o);const u=34*r,b=14*r,v=Bt*r,T=44*r,C=8*r,x=v+8*r,$=u+b+4*r,L=o-x-$;if(!s||!s.length){this._pgLoading&&(n.fillStyle=_,n.font=`${12*r}px sans-serif`,n.textAlign="center",n.textBaseline="middle",n.fillText(this._t("msg.loading",{},"Loading\u2026"),i/2,o/2));return}const D=(this._cycles||[]).find(E=>E.id===this._pgCycleId)?._pg_duration||s[s.length-1].t||1,B=Math.max(...s.map(E=>E.w),1),U=this._pgThreshStart??this._pgFieldVal("start_threshold_w",{})??50,z=this._pgThreshStop??this._pgFieldVal("stop_threshold_w",{})??5;let M=0,A=D;this._pgView&&this._pgView.max-this._pgView.min>1&&(M=Math.max(0,this._pgView.min),A=Math.min(D,this._pgView.max),A-M<=1&&(M=0,A=D));const O=Math.max(1e-6,A-M),K=i-T-C,I=E=>T+(E-M)/O*K,R=E=>x+(1-Math.max(0,E)/B)*L;this._pgMap={vMin:M,vMax:A,totalDur:D,padLpx:T/r,plotWpx:K/r},n.strokeStyle="rgba(127,127,127,0.12)",n.lineWidth=r,n.setLineDash([]);const G=Ie(B,5),W=[];for(let E=1;E*G<=B*1.0001;E++)W.push(E*G);W.forEach(E=>{const S=R(E);Sx+L||(n.beginPath(),n.moveTo(T,S),n.lineTo(i-C,S),n.stroke(),n.fillStyle=_,n.font=`${9*r}px sans-serif`,n.textAlign="right",n.textBaseline="middle",n.fillText(Ht(E,G),T-4*r,S))}),n.save(),n.beginPath(),n.rect(T,x,K,L),n.clip();const J=this._pgEnvData,Y=J&&Array.isArray(J.avg)?J.avg.filter(E=>Array.isArray(E)&&E.length>=2):[];if(Y.length){const E=Math.max(...Y.map(H=>H[0]))||1,S=H=>I(H/E*D),F=Array.isArray(J.min)?J.min.filter(H=>Array.isArray(H)&&H.length>=2):[],N=Array.isArray(J.max)?J.max.filter(H=>Array.isArray(H)&&H.length>=2):[];if(F.length&&N.length){n.beginPath(),N.forEach((H,Z)=>Z?n.lineTo(S(H[0]),R(H[1])):n.moveTo(S(H[0]),R(H[1])));for(let H=F.length-1;H>=0;H--)n.lineTo(S(F[H][0]),R(F[H][1]));n.closePath(),n.fillStyle="#eda10012",n.fill()}else n.beginPath(),n.strokeStyle="#eda100",n.lineWidth=2*r,n.setLineDash([5*r,4*r]),Y.forEach((H,Z)=>Z?n.lineTo(S(H[0]),R(H[1])):n.moveTo(S(H[0]),R(H[1]))),n.stroke(),n.setLineDash([])}n.beginPath(),n.moveTo(I(0),R(0)),s.forEach(E=>n.lineTo(I(E.t),R(E.w))),n.lineTo(I(s[s.length-1].t),R(0)),n.closePath(),n.fillStyle=Pt(c,.1),n.fill(),n.beginPath(),n.strokeStyle=c,n.lineWidth=2*r,s.forEach((E,S)=>S?n.lineTo(I(E.t),R(E.w)):n.moveTo(I(E.t),R(E.w))),n.stroke(),n.restore();const nt=(E,S,F)=>{const N=R(E);Nx+L+2||(n.save(),n.strokeStyle=S,n.lineWidth=2*r,n.setLineDash([8*r,4*r]),n.beginPath(),n.moveTo(T,N),n.lineTo(i-C,N),n.stroke(),n.setLineDash([]),n.fillStyle=S,n.beginPath(),n.arc(T+(i-T-C)*.06,N,5*r,0,Math.PI*2),n.fill(),n.fillStyle=S,n.font=`bold ${9*r}px sans-serif`,n.textAlign="left",n.textBaseline="bottom",n.fillText(F+" "+Ht(E),T+14*r,N-2*r),n.restore())};nt(+U,"#2a78d6",this._t("lbl.start",{},"Start")),nt(+z,"#e34948",this._t("btn.stop",{},"Stop"));const _t={idle:f,detecting:"#42a5f566",running:"#66bb6a66",ending:"#ef535066",anti_wrinkle:"#ab47bc66",stalled:"#eda10066"},ct={idle:this._t("lbl.pg_idle",{},"Idle"),detecting:this._t("lbl.pg_detecting",{},"Detecting"),running:this._t("lbl.pg_ev_running",{},"Running"),ending:this._t("lbl.pg_ev_ending",{},"Ending"),anti_wrinkle:this._t("lbl.pg_anti_wrinkle",{},"Anti-wrinkle"),stalled:this._t("status.stalled",{},"Stalled")},dt=o-u-b;n.fillStyle=f,n.fillRect(T,dt,i-T-C,u),n.save(),n.beginPath(),n.rect(T,dt,K,u),n.clip(),this._pgStateSegsFromSeries(D).forEach(E=>{const S=I(E.start),F=I(E.end);n.fillStyle=_t[E.state]||p,n.fillRect(S,dt,Math.max(1,F-S),u),F-S>50*r&&(n.fillStyle=_,n.font=`${8*r}px sans-serif`,n.textAlign="center",n.textBaseline="middle",n.fillText((ct[E.state]||E.state).toUpperCase(),(S+F)/2,dt+u/2))}),n.restore();const m=o-b,g=[];for(const E of this._pgDetail&&this._pgDetail.series||[]){const S=g[g.length-1];if(S&&S.name===(E.phase||null)){S.end=E.t;continue}S&&(S.end=E.t),g.push({name:E.phase||null,start:E.t,end:E.t})}g.some(E=>E.name)&&(n.save(),n.beginPath(),n.rect(T,m,K,b),n.clip(),g.filter(E=>E.name).forEach((E,S)=>{const F=I(E.start),N=I(E.end),H=S*47%360;n.fillStyle=`hsla(${H},60%,55%,0.55)`,n.fillRect(F,m,Math.max(1,N-F),b),N-F>40*r&&(n.fillStyle="#fff",n.font=`${7*r}px sans-serif`,n.textAlign="center",n.textBaseline="middle",n.fillText(E.name,(F+N)/2,m+b/2))}),n.restore()),this._pgEventHits=[];const k=this._pgDetail&&Array.isArray(this._pgDetail.events)?this._pgDetail.events.filter(E=>E.type!=="state"&&E.t>=M&&E.t<=A).slice().sort((E,S)=>E.t-S.t):[],y=11*r,P=x-v/2-2*r,X=y*2+3*r;let q=-1/0;const j=this._pgHoverEvent;if(k.forEach(E=>{const S=this._pgEventMeta(E.type),F=I(E.t);let N=Math.max(F,q+X);N=Math.max(T+y,Math.min(i-C-y,N)),q=N;const H=j&&j.t===E.t&&j.type===E.type;n.save(),n.strokeStyle=S.color,n.globalAlpha=H?.9:.5,n.lineWidth=(H?2:1)*r,n.beginPath(),n.moveTo(N,P+y),n.lineTo(F,x),n.stroke(),H&&(n.globalAlpha=.3,n.setLineDash([2*r,3*r]),n.beginPath(),n.moveTo(F,x),n.lineTo(F,o-b),n.stroke(),n.setLineDash([])),n.restore(),n.save(),n.beginPath(),n.arc(N,P,y,0,Math.PI*2),n.fillStyle=H?S.color:f||"#1a1a1a",n.fill(),n.lineWidth=(H?2:1.5)*r,n.strokeStyle=S.color,n.stroke(),n.fillStyle=H?"#fff":S.color,n.font=`${14*r}px sans-serif`,n.textAlign="center",n.textBaseline="middle",n.fillText(S.glyph,N,P+r),n.restore(),this._pgEventHits.push({cx:N/r,cy:P/r,r:y/r,type:E.type,label:S.label,detail:E.detail,t:E.t})}),j){const E=this._pgEventHits.find(S=>S.t===j.t&&S.type===j.type);if(E){const S=this._pgEventMeta(E.type),F=ft=>`${Math.floor(ft/60)}:${String(Math.round(ft%60)).padStart(2,"0")}`,N=this._pgEventDescription(E.type),H=[{t:E.label,bold:!0,col:S.color},...N?[{t:N}]:[],...E.detail?[{t:E.detail,dim:!0}]:[],{t:`${this._t("lbl.from_start",{},"From start")} ${F(E.t)}`,dim:!0}];n.save(),n.font=`${10*r}px sans-serif`;const Z=Math.min(220*r,i-2*T),tt=[];H.forEach(ft=>{const At=String(ft.t).split(" ");let bt="";At.forEach(Ot=>{const Jt=bt?bt+" "+Ot:Ot;n.measureText(Jt).width>Z&&bt?(tt.push({...ft,t:bt}),bt=Ot):bt=Jt}),tt.push({...ft,t:bt})});const st=14*r,rt=Math.min(Z+14*r,i-8*r),lt=tt.length*st+10*r;let pt=E.cx*r-rt/2;pt=Math.max(4*r,Math.min(i-rt-4*r,pt));const Rt=P+y+6*r;n.fillStyle=f,n.globalAlpha=.97,n.fillRect(pt,Rt,rt,lt),n.globalAlpha=1,n.strokeStyle=S.color,n.lineWidth=r,n.strokeRect(pt,Rt,rt,lt),n.textAlign="left",n.textBaseline="top",tt.forEach((ft,At)=>{n.font=`${ft.bold?"600 ":""}${10*r}px sans-serif`,n.fillStyle=ft.col||(ft.dim?_:(l.getPropertyValue("--primary-text-color")||"#ddd").trim()),n.fillText(ft.t,pt+7*r,Rt+5*r+At*st)}),n.restore()}}if(this._pgHoverT!=null&&this._pgHoverT>=M&&this._pgHoverT<=A){const E=I(this._pgHoverT);n.save(),n.strokeStyle="#e34948",n.lineWidth=1.5*r,n.setLineDash([4*r,3*r]),n.beginPath(),n.moveTo(E,x),n.lineTo(E,o-b),n.stroke(),n.setLineDash([]);const S=this._pgInterpPower(s,this._pgHoverT),F=R(S);n.fillStyle="#e34948",n.beginPath(),n.arc(E,F,3.5*r,0,Math.PI*2),n.fill();const N=at=>`${Math.floor(at/60)}:${String(Math.round(at%60)).padStart(2,"0")}`,H=at=>at>=1e3?it(at/1e3,2)+" kW":Lt(at),Z=[`${this._t("lbl.from_start",{},"From start")} ${N(this._pgHoverT)}`,`${this._t("lbl.to_end",{},"To end")} ${N(Math.max(0,D-this._pgHoverT))}`,`${this._t("lbl.power",{},"Power")} ${H(S)}`];n.font=`${9.5*r}px sans-serif`;const tt=Math.max(...Z.map(at=>n.measureText(at).width))+12*r,st=Z.length*13*r+8*r;let rt=E+8*r;rt+tt>i-C&&(rt=E-tt-8*r);const lt=x+4*r;n.fillStyle=f,n.globalAlpha=.95,n.fillRect(rt,lt,tt,st),n.globalAlpha=1,n.strokeStyle=p,n.lineWidth=r,n.strokeRect(rt,lt,tt,st),n.fillStyle=_,n.textAlign="left",n.textBaseline="top",Z.forEach((at,pt)=>n.fillText(at,rt+6*r,lt+5*r+pt*13*r)),n.restore()}n.fillStyle=_,n.font=`${9*r}px sans-serif`,n.textAlign="center",n.textBaseline="top";const V=E=>`${Math.floor(E/60)}:${String(Math.round(E%60)).padStart(2,"0")}`;for(let E=0;E<=4;E++){const S=M+O*E/4,F=I(S);n.textAlign=E===0?"left":E===4?"right":"center",n.fillText(V(S),Math.max(T,Math.min(i-C,F)),dt+u+1*r)}Oz.id===this._pgCycleId)?._pg_duration||e[e.length-1].t||1,r=t??a,i=this._pgInterpPower(e,r),o=this._pgSeriesAt(r),n=o?this._pgMapState(o.state,o.stalled):"idle",l={idle:[this._t("lbl.pg_idle",{},"Idle"),"var(--secondary-background-color)"],detecting:[this._t("lbl.pg_detecting",{},"Detecting"),"#42a5f5"],running:[this._t("lbl.pg_ev_running",{},"Running"),"#66bb6a"],ending:[this._t("lbl.pg_ev_ending",{},"Ending"),"#ef5350"],anti_wrinkle:[this._t("lbl.pg_anti_wrinkle",{},"Anti-wrinkle"),"#ab47bc"],stalled:[this._t("status.stalled",{},"Stalled"),"#eda100"]},[c,p]=l[n]||l.idle,_=o&&o.progress!=null?Math.round(o.progress):null,f=o&&o.remaining_s!=null?o.remaining_s:null,u=o&&o.energy_wh!=null?o.energy_wh:this._pgTrapEnergy(e,r),b=o&&o.confidence!=null?Math.round(o.confidence*100)+"%":"\u2014",v=o&&o.phase?o.phase:"\u2014",T=z=>Math.floor(z/60)+":"+String(Math.round(z%60)).padStart(2,"0"),C=z=>z>=1e3?it(z/1e3,2)+" kWh":it(z,0)+" Wh",x=z=>z>=1e3?it(z/1e3,1)+" kW":Lt(z),$=this.shadowRoot,L=z=>$&&$.getElementById(z),D=(z,M)=>{const A=L(z);A&&(A.textContent=M)},B=(z,M,A)=>{const O=L(z);O&&(O.style[M]=A)},U=L("wd-pg-state-badge");U&&(U.textContent=c,U.style.background=p,U.style.color=p.includes("var")?"":"#fff"),D("wd-pg-power",x(i)),D("wd-pg-pct",_!=null?_+"%":"\u2014%"),B("wd-pg-pbar","width",(_??0)+"%"),D("wd-pg-rem",f!=null?T(f):"\u2014"),D("wd-pg-energy",C(u)),D("wd-pg-conf",b),D("wd-pg-phase",v)}_pgIsUnknownCmd(t){const e=t&&(t.code||t.error&&t.error.code)||"",s=(t&&(t.message||t.error)||"").toString().toLowerCase();return e==="unknown_command"||s.includes("unknown command")||s.includes("unknown_command")}_pgInterpPower(t,e){if(!t.length)return 0;if(e<=t[0].t)return t[0].w;for(let s=1;s=e))break}return s}_drawPlaygroundCanvases(){this._tab==="playground"&&this._pgDrawCanvas()}_htmlPhases(){const t=this._devices[this._selIdx],e=t&&t.options.device_type||"washing_machine",s=this._canEdit(),a=this._phases.map(o=>{const n=o.is_default,l=o.translation_key?this._t(o.translation_key,{},o.description||""):o.description||"",c=s?`
${this._t("btn.edit",{},"Edit")}
${o.is_override?`${this._t("btn.reset",{},"Reset")} `:n?"":`${this._t("btn.delete",{},"Delete")} `}
`:"";return`
${d(o.name)} ${n?`${this._t("badge.built_in_tag",{},"built-in")} `:""}
${d(l.length>60?l.slice(0,57)+"\u2026":l)}
${c}
`}).join(""),r=s?`${this._t("lbl.actions",{},"Actions")} `:"",i=s?`${this._t("btn.new_phase",{},"+ New Phase")}
`:"";return`
${this._t("hdr.phase_catalog",{},"Phase Catalog")}
${this._t("msg.phase_catalog_intro",{},"Named segments of a cycle (Pre-wash, Heating, Spin\u2026). Assign them to a profile from its control panel.")}
${i}
${this._phases.length===0?`
${this._t("msg.no_phases",{},"No phases defined.")}
`:`
${this._t("lbl.name",{},"Name")} ${this._t("lbl.description",{},"Description")} ${r}${a}
`}
`}_htmlImportUndo(){const t=this._importUndo;if(!t||!t.created_at||!this._isAdmin())return"";const e=t.counts||{},s=(e.real_cycles||0)+(e.reference_cycles||0)+(e.backfill_cycles||0),a=this._busy.has("import-undo");return`
${this._t("msg.import_undo_hint",{when:et(t.created_at),profiles:e.profiles||0,cycles:s},"A restore point from before the last import ({when}) is kept: {profiles} profile(s) and {cycles} cycle(s).")}
${a?' ':""}${this._t("btn.undo_import",{},"Undo last import")}
`}_htmlDiagnostics(){const t=this._diag;let e;return t&&t._error?e=`${this._t("msg.diagnostics_load_failed",{error:t._error},"Could not load diagnostics: {error}")}
`:t?e=`
${t.total_cycles??"-"}
${this._t("lbl.cycles_count",{},"Cycles")}
${t.total_profiles??"-"}
${this._t("tab.profiles",{},"Profiles")}
${t.debug_traces_count??"-"}
${this._t("lbl.debug_traces",{},"Debug Traces")}
${t.file_size_kb!=null?t.file_size_kb.toFixed(1):"-"}
${this._t("lbl.file_kb",{},"File (kB)")}
`:e=`${this._t("msg.loading",{},"Loading\u2026")}
`,`
${this._t("hdr.storage_stats",{},"Storage Stats")}
${e}
${this._t("btn.refresh",{},"Refresh")}
${this._canFull()?`
${this._t("hdr.maintenance",{},"Maintenance Actions")}
${this._t("hdr.process_history",{},"Process History")} ${this._t("msg.process_history_hint",{},"Re-run matching on all stored cycles, refresh suggestions, retrain ML models (if enabled), recost cycles from recorded prices and recompute cycle health. Run it after a batch of reviews.")}
${this._t("btn.process_history",{},"Process Now")}
${this._t("hdr.clear_debug",{},"Clear Debug Traces")} ${this._t("msg.clear_debug_hint",{},"Remove stored debug data to free space.")}
${this._t("btn.clear_debug",{},"Clear Debug Data")}
${this._t("hdr.wipe_history",{},"Wipe History")} ${this._t("msg.wipe_history_warning",{},"Permanently delete all cycles and profiles. Cannot be undone.")}
${this._t("btn.wipe_all",{},"Wipe All Data")}
${this._t("hdr.export_import",{},"Export / Import")}
${this._t("msg.export_description",{},"Choose exactly which profiles, cycles, settings and more to export to JSON, or analyze a file and import only the parts you want.")}
${this._t("btn.export_selected",{},"Export (choose data)")}
${this._t("btn.import_json",{},"Import from JSON")}
${this._t("btn.export_all",{},"Quick export everything")}
${this._htmlImportUndo()}
${this._t("hdr.import_power_history",{},"Import power history")}
${this._t("msg.import_history_description",{},"Had a smart plug before WashData? Upload its power history or read it from Home Assistant. Detection runs over it, and past cycles appear in your Cycles list ready to name.")}
${this._t("btn.import_power_history",{},"Import power history")}
`:`${this._t("msg.maintenance_requires_access",{},"Maintenance and export/import require full access.")}
`}`}_maintLabel(t,e){const s={descale:this._t("maint.descale",{},"Descale"),filter_clean:this._t("maint.filter_clean",{},"Clean filter"),drum_clean:this._t("maint.drum_clean",{},"Clean drum"),bearing_service:this._t("maint.bearing_service",{},"Bearing service"),other:this._t("maint.other",{},"Other"),salt:this._t("maint.salt",{},"Refill salt"),rinse_aid:this._t("maint.rinse_aid",{},"Refill rinse aid"),lint_filter:this._t("maint.lint_filter",{},"Clean lint filter"),condenser_clean:this._t("maint.condenser_clean",{},"Clean condenser")};if(s[t])return s[t];const r=(this._maintenance&&this._maintenance.custom_tasks||[]).find(i=>i&&i.id===t);return r&&r.name?r.name:e||t}_maintStatusRows(t,e){if(Array.isArray(t.status))return t.status;const s=t.reminders||{},a=t.cycles_since||{};return e.map(r=>{const i=parseInt(s[r],10)||0,o=Math.max(0,parseInt(a[r],10)||0);return{id:r,custom:!1,cycles_interval:i,days_interval:0,cycles_since:o,days_since:null,due:i>0&&o>=i}}).filter(r=>r.cycles_interval>0)}_htmlMaintenance(){const t=this._canEdit(),e=this._maintenance;if(e&&e._error)return`${this._t("msg.maintenance_load_error",{error:e._error},"Could not load maintenance data: {error}")}
`;if(!e)return`${this._t("msg.loading",{},"Loading\u2026")}
`;const s=e.event_types&&e.event_types.length?e.event_types:["descale","filter_clean","drum_clean","bearing_service","other"],a=e.due||[],r=e.log||[],i=e.reminders||{},o=Array.isArray(e.custom_tasks)?e.custom_tasks:[],n=e.limits||{},l=parseInt(n.name_max,10)||60,c=parseInt(n.tasks_max,10)||20,p=Math.max(0,parseInt(e.lifetime_cycle_count,10)||0),_=this._canFull(),f=a.length?(()=>{const M=a.map(O=>this._maintLabel(O)).join(", "),A=t?`${a.map(O=>`${this._t("btn.log_done_task",{task:this._maintLabel(O)},"Log done: {task}")} `).join("")}
`:"";return`
${this._t("msg.maintenance_due",{items:M},"Maintenance due: {items}")}
${A}
`})():"",u=new Date().toISOString().slice(0,10),b=s.map(M=>`${d(this._maintLabel(M))} `).join("")+o.map(M=>`${d(M.name||M.id)} `).join(""),v=t?`
${this._t("hdr.add_maintenance",{},"Add Maintenance Event")}
${this._t("lbl.notes",{},"Notes")}
${this._t("btn.add_maintenance",{},"Add maintenance event")}
`:"",T=r.length?r.map(M=>{const A=t?`${this._t("btn.delete",{},"Delete")} `:"",O=M.notes?`${d(M.notes)}
`:"";return`
${d(this._maintLabel(M.event_type,M.task_name))}
${et(M.date)}
${O}
${A}
`}).join(""):`${this._t("msg.no_maintenance",{},"No maintenance recorded yet.")}
`,C=(M,A,O,K)=>` `,x=o.map(M=>`
${this._t("lbl.task_name",{},"Task")}
${this._t("lbl.every_cycles",{},"Every (cycles)")} ${C("data-mtask-cycles",M.id,M.cycles,n.cycles_max||1e5)}
${this._t("lbl.every_days",{},"Every (days)")} ${C("data-mtask-days",M.id,M.days,n.days_max||3650)}
${this._t("btn.remove",{},"Remove")}
`).join(""),$=o.length
${this._t("lbl.new_task_name",{},"New task")}
${this._t("lbl.every_cycles",{},"Every (cycles)")}
${this._t("lbl.every_days",{},"Every (days)")}
${this._t("btn.add_task",{},"Add task")}
`:`${this._t("msg.custom_task_limit",{max:c},"You have reached the limit of {max} tasks.")}
`,L=t?`
${this._t("hdr.maintenance_reminders",{},"Service Reminders")}
${this._t("msg.reminders_intro",{},"Show a reminder in the panel this many cycles after the last service. Leave blank or 0 to turn a reminder off.")}
${this._t("hdr.custom_tasks",{},"Your own tasks")}
${this._t("msg.custom_tasks_intro",{},"Add anything else you want to be reminded of. A task is due when either interval is reached; leave one blank or 0 to use only the other.")}
${x}
${$}
${this._t("msg.maintenance_due_sensor_hint",{},"The Maintenance due binary sensor is on while any task is due, for your own notification automations.")}
${this._t("btn.save_reminders",{},"Save reminders")}
`:"",D=this._maintStatusRows(e,s).map(M=>{const A=parseInt(M.cycles_interval,10)||0,O=parseInt(M.days_interval,10)||0,K=Math.max(0,parseInt(M.cycles_since,10)||0),I=M.days_since==null?null:Math.max(0,parseInt(M.days_since,10)||0),R=[];let G=0;if(A>0&&(R.push(this._t("lbl.cycles_since_service",{since:K,total:A},K+" / "+A+" cycles")),G=Math.max(G,K/A)),O>0){const Y=I??0;R.push(this._t("lbl.days_since_service",{since:Y,total:O},Y+" / "+O+" days")),G=Math.max(G,Y/O)}const W=Math.max(0,Math.min(100,Math.round(G*100))),J=M.due?"var(--warning-color,#ff9800)":"var(--primary-color)";return`
${d(M.custom?M.name||M.id:this._maintLabel(M.id))}
${R.join(" \xB7 ")}
`}).join(""),B=_?`
${this._t("lbl.correct_total_cycles",{},"Correct the total")}
${this._t("msg.odometer_correct_hint",{},"Set this if WashData counted a run that never happened, or the appliance ran cycles before WashData was installed.")}
${this._t("btn.save_total_cycles",{},"Save total")}
`:"",U=`
${this._t("hdr.service_status",{},"Service Status")}
${p}
${this._t("lbl.total_cycles_run",{},"cycles run in total")}
${this._t("msg.odometer_intro",{},"This total only goes up. Deleting a cycle does not lower it, so service reminders stay correct.")}
${D||`
${this._t("msg.no_reminders_set",{},"No service reminders are set yet.")}
`}
${B}
`,z=`
${this._t("hdr.maintenance_supporter",{},"Need more?")}
${this._t("msg.maintenance_supporter_intro",{},"Maintenance Supporter is a separate integration that does much more: any appliance or task, schedules by time, usage or a sensor, notifications and a history. It can use the WashData cycle counter.")}
${this._t("link.maintenance_supporter",{},"Maintenance Supporter on GitHub \u2197")}
`;return`${f}
${U}
${v}
${this._t("hdr.maintenance_log",{},"Maintenance Log")}
${this._t("msg.maintenance_intro",{},"Log servicing on this appliance and get reminded when each task is due again.")}
${T}
${L}
${z}`}_htmlPanel(){const t=this._canEdit(),e=t&&this._constants&&this._constants.mlTrainingAvailable,s=new Set(["diagnostics"]);e&&s.add("ml");let a=this._panelSubtab;s.has(a)||(a=this._panelSubtab="diagnostics");const r=[["diagnostics",this._t("tab.diagnostics",{},"Diagnostics")]];e&&r.push(["ml",this._t("tab.ml",{},"ML Training")]);const i=r.map(([n,l])=>`${l} `).join("");if(!t)return"";const o=a==="ml"&&e?this._htmlMlTab():this._htmlDiagnostics();return`${i}
${o}`}_levelSelect(t,e,s){const a=(s?[["inherit",this._t("access.inherit",{},"Inherit")]]:[]).concat([["none",this._t("access.none",{},"None (hidden)")],["read",this._t("access.read",{},"Read")],["edit",this._t("access.edit",{},"Edit")],["full",this._t("access.full",{},"Full")]]);return`${a.map(([r,i])=>`${i} `).join("")} `}_htmlPanelPrefs(){const t=this._panelCfg&&this._panelCfg.prefs||{},e=this._hass&&this._hass.locale&&this._hass.locale.language||"en",a=[["",this._t("pref.use_panel_default",{},"(panel default)")],["status",this._t("tab.status",{},"Overview")],["history",this._t("tab.history",{},"Cycles")],["profiles",this._t("tab.profiles",{},"Profiles")],["settings",this._t("tab.settings",{},"Settings")],["playground",this._t("tab.playground",{},"Playground")]].map(([l,c])=>`${d(c)} `).join(""),i=[["relative",this._t("pref.date_relative",{},"Relative (e.g. 2 hours ago)")],["absolute",this._t("pref.date_absolute",{},"Absolute (e.g. 14:32 on 2 Jul)")]].map(([l,c])=>`${d(c)} `).join(""),o=t.lang_override||"",n=[["",this._tText("pref.lang_auto",{lang:e.toUpperCase()},"System default ("+e.toUpperCase()+")")],["en",this._t("pref.lang_en",{},"English")]].map(([l,c])=>`${d(c)} `).join("");return`
${this._t("hdr.my_preferences",{},"My Preferences")}
${this._t("msg.prefs_personal",{},"These apply to your Home Assistant account only.")}
${this._t("hdr.display",{},"Display")}
${this._t("hdr.status_graph",{},"Status Graph")}
${kt(`id="wd-pref-expected" ${t.show_expected!==!1?"checked":""}`,this._t("lbl.show_expected",{},"Show expected curve overlay (matched profile, orange)"))}
${kt(`id="wd-pref-raw" ${t.show_raw?"checked":""}`,this._t("lbl.show_raw",{},"Show raw socket toggle in live power graph"))}
${this._t("hdr.diagnostics_pref",{},"Diagnostics")}
${kt(`id="wd-pref-debug" ${t.show_debug?"checked":""}`,this._t("lbl.show_debug",{},"Show live match debug card on the Status page (confidence, ambiguity, top candidates)"))}
${this._t("btn.save_preferences",{},"Save Preferences")}
`}_htmlPanelSettings(){const t=this._panelCfg&&this._panelCfg.panel||{},e=[["status",this._t("tab.status",{},"Overview")],["history",this._t("tab.history",{},"Cycles")],["profiles",this._t("tab.profiles",{},"Profiles")],["settings",this._t("tab.settings",{},"Settings")],["playground",this._t("tab.playground",{},"Playground")]],s=e.map(([i,o])=>`${d(o)} `).join(""),a=t.hidden_tabs||[],r=e.filter(([i])=>i!=="status").map(([i,o])=>Xt(`data-hidetab="${i}" ${a.includes(i)?"checked":""}`,o)).join("");return`
${this._t("hdr.panel_settings",{},"Panel Settings (all users)")}
${this._t("lbl.hide_tabs",{},"Hide tabs for non-admins")} ${r}
${this._t("btn.save_panel_settings",{},"Save Panel Settings")}
`}_htmlPanelAccess(){const t=this._panelCfg&&this._panelCfg.rbac||{enabled:!1,default_level:"none",users:{}},e=this._panelCfg&&this._panelCfg.users||[],s=this._devices||[],a=e.filter(i=>!i.is_admin).map(i=>{const o=(t.users||{})[i.id]||{default:"none",devices:{}},n=s.map(l=>`${d(l.title)} ${this._levelSelect(`data-rbacuser="${d(i.id)}" data-rbacdev="${d(l.entry_id)}"`,(o.devices||{})[l.entry_id]||"inherit",!0)}
`).join("");return`
${d(i.name)}
${this._t("lbl.default_other",{},"Default (other devices)")} ${this._levelSelect(`data-rbacuser="${d(i.id)}" data-rbacdev="__default__"`,o.default||"none",!1)}
${n}
`}).join(""),r=e.filter(i=>i.is_admin).map(i=>`${d(i.name)} - full (admin) `).join(" ");return`
${this._t("hdr.access_control",{},"Access Control")}
${kt(`id="wd-rbac-enabled" ${t.enabled?"checked":""}`,this._t("lbl.enable_access_control",{},"Enable per-user access control"),"",this._t("msg.rbac_hint",{},"Off: every Home Assistant user has full access (default). Administrators always have full access and can manage everyone."))}
${this._t("lbl.default_access_level",{},"Default level for users not listed below")} ${this._levelSelect('id="wd-rbac-default"',t.default_level||"none",!1)}
${r?`
${this._t("lbl.administrators",{},"Administrators")} ${r}
`:""}
${this._t("btn.save_access_control",{},"Save Access Control")}
${a||`${this._t("msg.no_other_users",{},"No other Home Assistant users found.")}
`}`}_htmlStore(){if(!this._canEdit())return`${this._t("msg.no_device_selected",{},"No device selected.")}
`;const t=this._storeStatus,e=`${this._t("store.website",{},"Store website \u2197")} `,s=`${this._t("hdr.community_store",{},"Community Store")}
${e}
`;if(!this._onlineEnabled()||t&&t.enabled===!1)return`${s}
${this._t("msg.store_enable_hint",{},"Turn on online features in Preferences > Online & Community to browse and import community reference cycles.")}
`;let a;return this._storeView==="device"?a=this._htmlStoreDevice():this._storeView==="profile"?a=this._htmlStoreProfile():a=this._htmlStoreBrands(),`
${s}
${this._htmlStoreCrumbs()}
${a}
`}_htmlStoreCrumbs(){const t=[`${this._t("store.browse",{},"Browse")} `];if(this._storeDevice){const e=this._storeDevice,s=`${e.brand||""} ${e.model||""}`.trim()||this._t("store.device",{},"Device");t.push('\u203A '),t.push(this._storeView==="device"?`${d(s)} `:`${d(s)} `)}return this._storeProfile&&this._storeView==="profile"&&(t.push('\u203A '),t.push(`${d(this._storeProfile.program||"")} `)),`${t.join("")}
`}_htmlStoreLoading(){return`\u23F3
${this._t("msg.loading",{},"Loading\u2026")}
`}_htmlStoreBrands(){const t=this._storeDevices||[],e=String((this._opts||{}).store_model||"").trim().toLowerCase(),s=t.map(o=>{const n=`${d(o.brand||"")} ${d(o.model||"")}`.trim()||this._t("store.device",{},"Device"),l=o.applianceType?`${d(this._deviceTypeLabel(o.applianceType))} `:"",p=e&&String(o.model||"").toLowerCase()===e?`${this._t("store.your_model",{},"Yours")} `:"",_=Dt(o.profileCount),f=_>0?`${this._t("store.programs_count",{n:_},`Programs: ${_}`)} `:"";return`
${n}${p}${this._statusTag(o)}
${l}${f}\u2605 ${it(Dt(o.favoriteCount),0)}
\u203A
`}).join("");if(!this._storeBrandScope())return`
${this._storeSearchHtml()}
${this._t("msg.store_declare_appliance",{},"Tell WashData which appliance you own to see setups others shared for it. Or type a brand above to look around.")}
${this._t("btn.set_brand_model",{},"Set brand & model")} `;const a=`${this._t("store.no_results",{},"No matching appliances found. Try a different search.")}
`,r=this._storeLoading?this._htmlStoreLoading():t.length?`${s}
`:a,i=!this._storeLoading&&t.length>1?`${this._t("msg.store_sibling_hint",{},"Nothing shared for your model? A related model from the same brand is usually a good start.")}
`:"";return`
${this._storeSearchHtml()}
${i}
${r}`}_storeSearchHtml(){return`
`}_htmlStoreUnreachable(){return`${this._t("store.unreachable",{},"Couldn't reach the community store. Check your connection and try again.")}
`}_htmlStoreDevice(){const t=this._storeProfiles||[],e=t.map(o=>`
${d(o.program||"")}${this._statusTag(o)}
\u203A
`).join(""),s=this._storeLoading?this._htmlStoreLoading():t.length?`${e}
`:this._storeBrowseErr?this._htmlStoreUnreachable():`${this._t("store.no_programs",{},"No shared programs for this appliance yet.")}
`,a=this._storeDevice,r=this._busy.has("store-download-device");return(this._canEdit()&&a&&t.length?`
${this._t("msg.store_download_device_intro",{},"Add every shared program and its reference cycles to your device. Your own cycles and stats are not affected.")}
${Xt(`data-action="store-toggle-dl-settings" ${this._dlSettings?"checked":""} ${r?"disabled":""}`,this._t("lbl.adopt_settings",{},"Also adopt settings"),ot(this._t("msg.adopt_settings_hint",{},"Overwrite this device's detection & matching thresholds with the shared ones. Your notifications, entities and energy price are never changed.")))}
${r?' ':"\u2B07 "}${this._t("btn.download_device",{},"Download this setup")}
`:"")+s}_htmlStoreProfile(){const t=this._storeCycles||[],e=t.map(a=>{const r=a.stats&&typeof a.stats=="object"?a.stats:{},i=this._storeSparkline(a.trace&&a.trace.points||[]),o=ht(r.duration),n=gt(r.energy_wh!=null?Number(r.energy_wh)/1e3:null),l=Lt(r.peak_w),c=typeof a.uploaderName=="string"&&a.uploaderName||this._tText("store.anon",{},"anonymous"),p=this._statusTag(a),_=a.rating&&typeof a.rating=="object"?a.rating:{},f=Number(_.avg),u=Dt(_.count),b=_.avg!=null&&isFinite(f)&&u>0?this._t("store.rating_summary",{avg:it(f,1),n:u},"\u2605 {avg} ({n})"):this._t("store.no_ratings",{},"No ratings yet");return`
${i}
${o} \xB7 ${n} \xB7 ${l} ${p}
${this._t("store.uploaded_by",{name:c},"Shared by {name}")} \xB7 \u2B07 ${it(Dt(a.downloads),0)} \xB7 ${b}
${this._t("btn.import",{},"Import")}
`}).join("");return`${this._storeLoading?this._htmlStoreLoading():t.length?e:this._storeBrowseErr?this._htmlStoreUnreachable():`
${this._t("store.no_cycles",{},"No reference cycles shared for this program yet.")}
`}
`}_storeSparkline(t){const e=Array.isArray(t)?t.filter(u=>Array.isArray(u)&&u.length>=2).map(u=>[Number(u[0]),Number(u[1])]).filter(u=>isFinite(u[0])&&isFinite(u[1])):[];if(e.length<2)return' ';const s=e.map(u=>u[0]),a=e.map(u=>u[1]),r=Math.min(...s),i=Math.max(...s),o=Math.max(1,...a),n=120,l=36,c=2,p=u=>c+(i>r?(u-r)/(i-r):0)*(n-2*c),_=u=>l-c-Math.max(0,u)/o*(l-2*c),f=e.map(u=>`${p(u[0]).toFixed(1)},${_(u[1]).toFixed(1)}`).join(" ");return` `}_htmlGearModal(t){const e=this._isAdmin(),s=[["prefs",this._t("hdr.my_preferences",{},"My Preferences")]];e&&s.push(["panel",this._t("hdr.panel_settings",{},"Panel Settings")],["access",this._t("hdr.access_control",{},"Access Control")]),e&&this._constants&&this._constants.storeOnlineAvailable&&s.push(["online",this._t("hdr.online_account_tab",{},"Online & Community")]);let a=t.tab;s.some(([o])=>o===a)||(a=t.tab="prefs");const r=s.map(([o,n])=>`${n} `).join(""),i=a==="panel"&&e?this._htmlPanelSettings():a==="access"&&e?this._htmlPanelAccess():a==="online"&&e?this._htmlOnlineSettings():this._htmlPanelPrefs();return` ${this._t("settings.gear.title",{},"Preferences")}\u2715
${r}
${i}
`}_htmlOnlineSettings(){if(!(this._constants&&this._constants.storeOnlineAvailable))return`${this._t("msg.online_unavailable",{},"Online features are not available on this server.")}
`;const t=this._onlineEnabled(),e=this._storeStatus||{},s=!!(t&&e.connected),a=this._busy.has("store-account"),r=t?s?`
${this._t("store.connected_as",{name:e.name||e.uid||""},"Connected as {name}")}
${this._t("btn.disconnect",{},"Disconnect")}
`:`
${this._t("store.not_connected",{},"Not connected. Connect a GitHub account to confirm appliances and share your own cycles.")}
${this._t("btn.connect_github",{},"Connect GitHub")}
`:"";return`
${this._t("hdr.online_account",{},"Community Store & online features")}
${this._t("msg.online_intro_global",{},"Browse and share reference recordings with other WashData users. One connection covers all your WashData devices; brand and model are set per device under Basic. Online features are off until you turn them on.")}
${kt(`data-action="store-toggle-online" ${t?"checked":""} ${a?"disabled":""}`,this._t("lbl.enable_online",{},"Enable online features"))}
${t?this._htmlStorePrefs(a):""}
${t?r:""}
`}_htmlStorePrefs(t){const e=this._constants&&this._constants.storePrefs||{},s=ke.map(r=>{const i=e[r.key],o=i===void 0?r.def!==!1:i!==!1;return kt(`data-action="store-toggle-pref" data-pref="${d(r.key)}" ${o?"checked":""} ${t?"disabled":""}`,this._t(r.labelKey,{},r.labelFb),ot(this._t(r.docKey,{},r.docFb)))}).join(""),a=this._busy.has("store-refresh-catalog");return s+`
${a?' ':""}${this._t("btn.refresh_catalog",{},"Refresh catalog")}
${this._t("msg.refresh_catalog_hint",{},"Community brand and appliance lists are cached to save the store's daily budget. Refresh to see entries others added or approved.")}
`}async _loadStoreStatus(t){if(!this._onlineEnabled()){this._storeStatus={enabled:!1},this._storeConnected=!1;return}try{const e=await this._ws({type:`${w}/store_status`,entry_id:t});if(!this._isActiveEntry(t))return;this._storeStatus=e||null,this._storeConnected=!!(e&&e.connected)}catch{}}_storeBrandScope(){return String(this._storeQuery||(this._opts||{}).store_brand||"").trim()}_renderKeepingStoreQFocus(){const t=this.shadowRoot,e=t&&t.getElementById("wd-store-q"),s=!!e&&t.activeElement===e,a=s?e.selectionStart:0;if(this._render(),!s)return;const r=t.getElementById("wd-store-q");if(!r)return;r.focus();try{r.setSelectionRange(a,a)}catch{}const i=r.parentElement&&r.parentElement.querySelector(".wd-combo-drop");i&&(i.hidden=!0)}_storeOpenDevice(t){const e=this._devices[this._selIdx];if(!e||!t)return;const s=e.entry_id;this._storeDevice=t,this._storeProfile=null,this._storeView="device",this._storeProfiles=[],this._storeCycles=[],this._storeLoading=!0,this._storeBrowseErr=!1,this._render(),this._ws({type:`${w}/store_get_profiles`,entry_id:s,device_id:t.id}).then(a=>{!this._isActiveEntry(s)||this._storeView!=="device"||(this._storeProfiles=a&&a.items||[],this._storeBrowseErr=!!(a&&a.error))}).catch(()=>{this._isActiveEntry(s)&&(this._storeProfiles=[],this._storeBrowseErr=!0)}).finally(()=>{this._isActiveEntry(s)&&(this._storeLoading=!1,this._render())})}async _storeOpenModel(t){if(!t)return;await this._storeSearch(String(t.brand||""));const e=(this._storeDevices||[]).find(s=>String(s.id)===String(t.id))||t;this._storeOpenDevice(e)}async _storeSearch(t){const e=this._devices[this._selIdx];if(!e)return;const s=e.entry_id;this._storeQuery=t||"",this._storeView="brands",this._storeDevice=null,this._storeProfile=null,this._storeProfiles=[],this._storeCycles=[];const a=this._storeBrandScope();if(!a){this._storeDevices=[],this._storeLoading=!1,this._renderKeepingStoreQFocus();return}this._storeLoading=!0,this._renderKeepingStoreQFocus();try{const r=await this._ws({type:`${w}/store_search_devices`,entry_id:s,query:a,appliance_type:this._storeApplianceType(),include_pending:!0});if(!this._isActiveEntry(s))return;r&&r.disabled?(this._storeStatus={enabled:!1},this._storeDevices=[]):this._storeDevices=this._sortStoreDevices(r&&r.items||[])}catch(r){this._isActiveEntry(s)&&(this._storeDevices=[],this._showToast(this._tText("toast.store_search_failed",{error:r.message||r},"Search failed: "+(r.message||r)),"error"))}finally{this._isActiveEntry(s)&&(this._storeLoading=!1,this._renderKeepingStoreQFocus())}}_sortStoreDevices(t){const e=String((this._opts||{}).store_model||"").trim().toLowerCase(),s=a=>e&&String(a.model||"").toLowerCase()===e?0:this._storeItemHasContent(a)?1:2;return t.map((a,r)=>({d:a,i:r})).sort((a,r)=>s(a.d)-s(r.d)||a.i-r.i).map(a=>a.d)}_storeItemHasContent(t){return(Number(t&&t.profileCount)||0)>0||(Number(t&&t.cycleCount)||0)>0}_ensureStoreConnectListener(){if(this._storeConnectListener)return;const t=this._constants.storeWebOrigin;if(!t)return;let e;try{e=new URL(t).origin}catch{return}this._storeConnectListener=async s=>{if(s.origin!==e)return;const a=s.data;if(!a)return;const r=this._devices[this._selIdx];if(!r)return;const i=r.entry_id;if(a.type==="washdata-device-created"){const o={};a.brand&&(o.store_brand=a.brand),a.model&&(o.store_model=a.model),this._catalog.brands=void 0,this._catalog.devices=void 0,this._catalog.forBrand=null,this._catalog.brandsFull=!1,this._catalog.brandPrefixes=[],this._dropModelCandidates(),this._catalogEntry=null;const n=this._storeContribEid||i,l=Object.keys(o).length>0,c=this._isActiveEntry(n);this._showToast(l&&!c?this._t("toast.contrib_device_changed",{},"Added to the community store, but the device changed while the form was open. Select it in Settings."):this._t("toast.appliance_added",{},"Appliance added - awaiting approval")),l&&c&&await this._saveStoreOptions(o,{silent:!0}),this._render();return}if(a.type==="washdata-brand-created"){this._catalog.brands=void 0,this._catalog.brandsFull=!1,this._catalog.brandPrefixes=[],this._dropModelCandidates(),this._catalogEntry=null;const o=this._isActiveEntry(this._storeContribEid||i);this._showToast(a.brand&&!o?this._t("toast.contrib_device_changed",{},"Added to the community store, but the device changed while the form was open. Select it in Settings."):this._t("toast.brand_added",{},"Brand added - awaiting approval")),a.brand&&o&&await this._saveStoreOptions({store_brand:a.brand,store_model:""},{silent:!0}),this._render();return}if(a.type==="washdata-profile-created"){const o=this._modal;o&&o.type==="store-share"&&(o.program=a.program||o.program,this._loadShareProfiles()),this._showToast(this._t("toast.profile_added",{},"Profile added - awaiting approval"));return}if(a.type==="washdata-connect")try{const o=await this._ws({type:`${w}/store_connect`,entry_id:i,refresh_token:a.refreshToken,uid:a.uid,name:a.displayName});if(o&&o.error){this._showToast(this._tText("toast.store_connect_failed",{error:o.error},"Connect failed: "+o.error),"error");return}if(await this._loadStoreStatus(i),!this._isActiveEntry(i))return;this._showToast(this._t("toast.store_connected",{},"Connected to the community store")),this._render()}catch(o){this._showToast(this._tText("toast.store_connect_failed",{error:o.message||o},"Connect failed: "+(o.message||o)),"error")}},window.addEventListener("message",this._storeConnectListener)}async _saveStoreOptions(t,{silent:e=!1}={}){const s=this._devices[this._selIdx];if(!s)return!1;const a=s.entry_id;try{if(await this._ws({type:`${w}/set_options`,entry_id:a,options:t}),!this._isActiveEntry(a))return!0;this._opts={...this._opts,...t};for(const r of Object.keys(t))delete this._pendingSettings[r],this._dirtyOptKeys.delete(r);return e||this._showToast(this._t("toast.settings_saved",{},"Settings saved; integration reloading")),!0}catch(r){return this._showToast(this._tText("msg.toast_save_failed",{error:r.message||r},"Save failed: "+(r.message||r)),"error"),!1}}_htmlLogDrawer(){return`
${this._t("hdr.logs",{},"Logs")}
${this._t("btn.export",{},"Export")}
\u2715
${this._htmlLogFilters("drawer")}
${this._t("msg.log_buffer_hint",{},"Newest first \xB7 buffers the last 500 ha_washdata records since restart \xB7 drag the left edge to resize.")}
${this._logLinesHtml()}
`}_drawCurves(t,e){const s=this.shadowRoot&&this.shadowRoot.getElementById(t);if(!s)return null;const a=s.getBoundingClientRect(),r=window.devicePixelRatio||1,i=Math.max(1,Math.round(a.width*r)),o=Math.max(1,Math.round((a.height||240)*r));(s.width!==i||s.height!==o)&&(s.width=i,s.height=o);const n=s.getContext("2d"),l=getComputedStyle(this),c=(l.getPropertyValue("--primary-color")||"#03a9f4").trim()||"#03a9f4",p=(l.getPropertyValue("--divider-color")||"rgba(127,127,127,.3)").trim()||"rgba(127,127,127,.3)",_=(l.getPropertyValue("--secondary-text-color")||"#888").trim()||"#888",f=44*r,u=12*r,b=12*r,v=22*r,T=e.series||[];let C=e.xMax||0;C||(T.forEach(A=>(A.points||[]).forEach(O=>{O[0]>C&&(C=O[0])})),e.band&&(e.band.max||[]).forEach(A=>{A[0]>C&&(C=A[0])})),C=C||1;const x=Math.min(0,Number(e.xStart)||0),$=this._canvasZoom&&this._canvasZoom[t],L=$?$.xMin:x,D=$?$.xMax:C;let B=e.yMax||0;if(!B){const A=O=>{const K=O||[];for(let I=0;I=L)||R>D&&!(I>0&&K[I-1][0]<=D)||K[I][1]>B&&(B=K[I][1])}};T.forEach(O=>{O.noScale||A(O.points)}),e.band&&A(e.band.max),B=(B||10)*1.08}const U=i-f-u,z=A=>f+(A-L)/(D-L)*U,M=A=>o-v-A/B*(o-b-v);n.clearRect(0,0,i,o),n.strokeStyle=p,n.lineWidth=r,n.fillStyle=_,n.font=`${11*r}px sans-serif`,n.textAlign="right",n.textBaseline="middle";for(let A=0;A<=2;A++){const O=b+A/2*(o-b-v);n.beginPath(),n.moveTo(f,O),n.lineTo(i-u,O),n.stroke(),n.fillText(Ht(B*(1-A/2),B/2),f-4*r,O)}if(n.save(),n.beginPath(),n.rect(f,b,U,o-b),n.clip(),(e.bands||[]).forEach(A=>{n.fillStyle=A.fill;const O=z(A.x0),K=z(A.x1);n.fillRect(Math.min(O,K),b,Math.abs(K-O),o-b-v)}),e.band&&(e.band.min||[]).length&&(e.band.max||[]).length){n.beginPath(),e.band.max.forEach((A,O)=>O?n.lineTo(z(A[0]),M(A[1])):n.moveTo(z(A[0]),M(A[1])));for(let A=e.band.min.length-1;A>=0;A--)n.lineTo(z(e.band.min[A][0]),M(e.band.min[A][1]));n.closePath(),n.fillStyle=e.band.fill||Pt(c,.13),n.fill()}return T.forEach(A=>{const O=A.points||[];if(!O.length)return;const K=A.stroke==="primary"?c:A.stroke;if(A.fill){n.beginPath(),O.forEach((G,W)=>W?n.lineTo(z(G[0]),M(G[1])):n.moveTo(z(G[0]),M(G[1]))),n.lineTo(z(O[O.length-1][0]),M(0)),n.lineTo(z(O[0][0]),M(0)),n.closePath();const R=n.createLinearGradient(0,b,0,o-v);R.addColorStop(0,Pt(K,.33)),R.addColorStop(1,Pt(K,.03)),n.fillStyle=R,n.fill()}n.beginPath();let I=!1;O.forEach(R=>{if(R[1]==null){I=!1;return}I?n.lineTo(z(R[0]),M(R[1])):(n.moveTo(z(R[0]),M(R[1])),I=!0)}),n.strokeStyle=K,n.lineWidth=(A.width||1.5)*r,n.lineJoin="round",A.dash&&n.setLineDash([6*r,4*r]),n.globalAlpha=A.alpha!=null?A.alpha:1,n.stroke(),n.globalAlpha=1,A.dash&&n.setLineDash([])}),n.textAlign="center",n.textBaseline="top",(e.vlines||[]).forEach(A=>{const O=z(A.x);n.beginPath(),n.moveTo(O,b),n.lineTo(O,o-v),n.strokeStyle=A.color,n.lineWidth=2*r,n.setLineDash([4*r,3*r]),n.stroke(),n.setLineDash([]),A.handle&&(n.fillStyle=A.color,n.beginPath(),n.arc(O,b+4*r,4.5*r,0,Math.PI*2),n.fill()),A.label&&(n.fillStyle=A.color,n.fillText(A.label,O,b+(A.handle?12*r:2*r)))}),n.restore(),n.fillStyle=_,n.font=`${11*r}px sans-serif`,n.textBaseline="bottom",($||x<0)&&(n.textAlign="left",n.fillText((L/60).toFixed($?1:0)+" min",f,o-2*r)),n.textAlign="right",n.fillText((D/60).toFixed(0)+" min",i-u,o-2*r),s._wd={xMax:C,xMin:L,xViewMax:D,yMax:B,dpr:r,padT:b,padB:v,padL:f,ch:o,primary:c,xStart:x,cycleSpan:e.cycleSpan||null,plotWcss:U/r,Xpx:z,Ypx:M,xToCss:A=>z(A)/r,cssToX:A=>Math.max(L,Math.min(D,L+(A*r-f)/U*(D-L))),series:(e.series||[]).map(A=>({points:A.points,stroke:A.stroke,name:A.name,cid:A.cid,bounded:A.bounded,aside:A.aside,kind:A.kind})),band:e.band||null,artifacts:e.artifacts||null,_opts:e},s._wd}_drawModalCanvas(){const t=this._modal;t&&(t.type==="cycle-detail"?this._drawCycleEditor():t.type==="compare-cycles"?this._drawCompareCanvas():t.type==="profile-group"?this._drawGroupCanvas():t.type==="profile-panel"&&(t.tab==="stats"?this._drawProfileEnvelope():t.tab==="phases"?this._drawPhaseEditor():t.tab==="cleanup"&&this._drawSpaghetti()))}_redrawCanvas(t){if(t==="wd-status-canvas")this._drawStatusCurve();else if(t==="wd-cyc-canvas")this._drawCycleEditor();else if(t==="wd-compare-canvas")this._drawCompareCanvas();else if(t==="wd-env-canvas")this._drawProfileEnvelope();else if(t==="wd-phase-canvas")this._drawPhaseEditor();else if(t==="wd-spag-canvas")this._drawSpaghetti();else if(t==="wd-pgroup-canvas")this._drawGroupCanvas();else{const e=this.shadowRoot&&this.shadowRoot.getElementById(t);e&&e._wd&&e._wd._opts&&this._drawCurves(t,e._wd._opts)}}_pref(t,e){const s=this._panelCfg&&this._panelCfg.prefs||{};return s[t]===void 0?e:s[t]}_setPref(t,e){this._panelCfg||(this._panelCfg={}),this._panelCfg.prefs={...this._panelCfg.prefs||{},[t]:e},this._ws({type:`${w}/set_user_prefs`,prefs:{[t]:e}}).catch(()=>{})}_drawStatusCurve(){const t=this._powerData||{},e=t.live||[];if(e.length<2)return;const s=this._statusEnv,a=this._pref("show_expected",!0),r=this._pref("show_raw_active",!1),i=[];let o=e[e.length-1][0];if(t.cycle_active&&s&&(s.avg||[]).length&&a){const l=s.target_duration||s.avg[s.avg.length-1][0];i.push({points:s.avg,stroke:"#ff9800",width:2,alpha:.4,name:this._t("lbl.expected",{},"Expected")}),o=Math.max(o,l)}i.push({points:e,stroke:"primary",fill:!0,width:2,name:this._t("lbl.power",{},"Power")}),r&&(t.raw||[]).length>1&&i.push({points:t.raw,stroke:"#9e9e9e",width:1,alpha:.65,name:this._t("lbl.raw_socket",{},"Raw socket"),noScale:!0});const n=[];(t.restart_gaps||[]).forEach(l=>{if(!e.length)return;const c=t.cycle_start_iso;if(!c)return;const p=new Date(c).getTime(),_=Math.max(0,(new Date(l.start_ts).getTime()-p)/1e3),f=Math.max(_+1,(new Date(l.end_ts).getTime()-p)/1e3);n.push({x0:_,x1:f,fill:"rgba(96,125,139,.20)"})}),this._drawCurves("wd-status-canvas",{series:i,xMax:o,bands:n})}_attachGraphGestures(t){const e=this.shadowRoot,s=e&&e.getElementById(t);if(!s)return;this._syncZoomReset(t),this._attachTouchOwnershipGuard(s);const a=new Map;let r=null;const i=n=>n-s.getBoundingClientRect().left;s.addEventListener("pointerdown",n=>{if(a.set(n.pointerId,{x:n.clientX,y:n.clientY}),a.size===1){if(n.pointerType==="touch"&&this._unpinGraphTip(),n.pointerType==="touch"&&this._onAxisHandle(s,t,n)){s._wdOwnGesture=!0;try{s.setPointerCapture(n.pointerId)}catch{}}return}if(a.size!==2)return;s._wdPinching=!0,s._wdOwnGesture=!1,s._wdAbortEdit&&s._wdAbortEdit(),this._hideGraphTip();const[l,c]=[...a.values()];r={dist:Math.max(1,Math.abs(l.x-c.x)),centerCss:i((l.x+c.x)/2)}}),s.addEventListener("pointermove",n=>{if(a.has(n.pointerId)&&a.set(n.pointerId,{x:n.clientX,y:n.clientY}),s._wdPinching){r&&a.size>=2&&this._onGraphPinch(s,t,a,r,i);return}this._onGraphHover(n,t)});const o=n=>{a.delete(n.pointerId),a.size<2&&(r=null,s._wdPinching=!1),a.size===0&&(s._wdOwnGesture=!1)};s.addEventListener("pointerup",n=>{const l=s._wdPinching;o(n),n.pointerType==="touch"&&!l&&this._pinGraphTip()}),s.addEventListener("pointercancel",n=>{o(n),this._hideGraphTip()}),s.addEventListener("pointerleave",n=>{n.pointerType!=="touch"&&this._hideGraphTip()}),s.addEventListener("wheel",n=>{const l=s._wd;if(!l)return;n.preventDefault();const c=n.ctrlKey?Math.min(4,Math.max(.25,Math.exp(n.deltaY*.01))):n.deltaY>0?1.3:.75;this._zoomCanvasAbout(t,l.cssToX(i(n.clientX)),c)},{passive:!1}),s.addEventListener("dblclick",n=>{const l=s._wd;if(l){if(this._canvasZoom[t]){this._resetCanvasZoom(t),this._hideGraphTip();return}this._zoomCanvasAbout(t,l.cssToX(i(n.clientX)),Qt)}})}_attachTouchOwnershipGuard(t){t._wdTouchGuard||(t._wdTouchGuard=!0,t.addEventListener("touchmove",e=>{(e.touches.length>=2||t._wdOwnGesture)&&e.preventDefault()},{passive:!1}))}_onAxisHandle(t,e,s){const a=this._gAxisHandle?this._gAxisHandle[e]:null,r=t._wd;if(a==null||!r)return!1;const i=t.getBoundingClientRect(),o=(r.ch-r.padB)/r.dpr;return Math.abs(s.clientX-i.left-a)<=Tt&&Math.abs(s.clientY-i.top-o)<=Tt}_onGraphPinch(t,e,s,a,r){const i=t._wd;if(!i)return;const[o,n]=[...s.values()],l=Math.max(1,Math.abs(o.x-n.x)),c=r((o.x+n.x)/2),p=this._gPinchPending&&this._gPinchPending.id===e?this._gPinchPending:null,_=p?{xMin:p.lo,xMax:p.hi}:this._canvasZoom[e],f=_?_.xMin:i.xStart||0,u=(_?_.xMax:i.xMax||1)-f,b=Math.max(1,i.plotWcss),v=Math.max(0,Math.min(1,c/b)),T=f+v*u,C=u*(a.dist/l),x=T-v*C-(c-a.centerCss)/b*C;a.dist=l,a.centerCss=c,this._gPinchPending={id:e,lo:x,hi:x+C},!this._gPinchRaf&&(this._gPinchRaf=requestAnimationFrame(()=>{this._gPinchRaf=null;const $=this._gPinchPending;this._gPinchPending=null,$&&this._setCanvasViewport($.id,$.lo,$.hi)}))}_setCanvasViewport(t,e,s){const a=this.shadowRoot&&this.shadowRoot.getElementById(t),r=a&&a._wd;if(!r)return;const i=r.xStart||0,o=(r.xMax||1)-i,n=Math.min(o,Math.max(o/Se,Te)),l=Math.min(o,Math.max(n,s-e));if(l>=o*.99){this._resetCanvasZoom(t);return}const c=Math.max(i,Math.min(i+o-l,e)),p=this._canvasZoom[t];p&&Math.abs(p.xMin-c)<1e-6&&Math.abs(p.xMax-c-l)<1e-6||(this._canvasZoom[t]={xMin:c,xMax:c+l},this._redrawCanvas(t),this._syncZoomReset(t))}_zoomCanvasAbout(t,e,s){const a=this.shadowRoot&&this.shadowRoot.getElementById(t),r=a&&a._wd;if(!r)return;const i=r.xMax||1,o=this._canvasZoom[t],n=o?o.xMin:r.xStart||0,l=Math.max(1e-6,(o?o.xMax:i)-n),c=Math.max(0,Math.min(1,(e-n)/l)),p=l*s;this._setCanvasViewport(t,e-c*p,e+(1-c)*p)}_resetCanvasZoom(t){if(!this._canvasZoom[t]){this._syncZoomReset(t);return}delete this._canvasZoom[t],this._redrawCanvas(t),this._syncZoomReset(t)}_ensureZoomReset(t,e){const s=e.parentElement;if(!s)return null;let a=s.querySelector(".wd-zoom-reset");a||(a=document.createElement("button"),a.type="button",a.className="wd-zoom-reset",a.textContent="\u21BB",a.addEventListener("click",i=>{i.stopPropagation(),this._resetCanvasZoom(t)}),s.appendChild(a));const r=this._t("btn.reset_zoom",{},"Reset zoom");return a.title!==r&&(a.title=r,a.setAttribute("aria-label",r)),a}_syncZoomReset(t){const e=this.shadowRoot&&this.shadowRoot.getElementById(t);if(!e)return;const s=this._ensureZoomReset(t,e);s&&s.classList.toggle("wd-zoom-reset--on",!!this._canvasZoom[t])}_onGraphHover(t,e){const s=t.clientX,a=t.clientY,r=t.pointerType==="touch";if(this._hoverRafId){this._hoverPending={px:s,py:a,id:e,touch:r};return}this._hoverPending={px:s,py:a,id:e,touch:r},this._hoverRafId=requestAnimationFrame(()=>{this._hoverRafId=null;const{px:i,py:o,id:n,touch:l}=this._hoverPending||{};n&&this._onGraphHoverInner(i,o,n,{touch:l})})}_onGraphHoverInner(t,e,s,a={}){const r=this.shadowRoot&&this.shadowRoot.getElementById(s),i=r&&r._wd;if(!i)return;const o=r.getBoundingClientRect(),n=i.cssToX(t-o.left),l=(e-o.top)*i.dpr;this._redrawCanvas(s);const c=r.getContext("2d"),p=i.Xpx(n);c.save(),c.strokeStyle="rgba(140,140,140,.75)",c.lineWidth=i.dpr,c.setLineDash([3*i.dpr,3*i.dpr]),c.beginPath(),c.moveTo(p,i.padT),c.lineTo(p,i.ch-i.padB),c.stroke(),c.setLineDash([]);const _=$=>$.stroke==="primary"?i.primary:$.stroke,f=($,L)=>{c.fillStyle=L,c.beginPath(),c.arc(p,i.Ypx($),3.4*i.dpr,0,6.2832),c.fill()},u=i.cycleSpan,b=u&&n${$t(u[0]-n)}`]:u&&n>u[1]?[`${this._t("lbl.after_end",{},"After end")}: ${$t(n-u[1])} `]:[`${this._t("lbl.from_start",{},"From start")}: ${$t(n)} `,`${this._t("lbl.to_end",{},"To end")}: ${$t(Math.max(0,(u?u[1]:i.xMax)-n))} `],v=i.series||[],T=$=>$.bounded?Ue($.points,n):Gt($.points,n);if(this._hoverNearest=null,v.filter($=>!$.aside).length>4){let $=null,L=1/0;if(v.forEach(D=>{const B=T(D);if(B==null)return;const U=Math.abs(i.Ypx(B)-l);UU?c.lineTo(i.Xpx(B[0]),i.Ypx(B[1])):c.moveTo(i.Xpx(B[0]),i.Ypx(B[1]))),c.stroke(),f($.v,D),b.push(`${d($.s.name||"")}: ${it($.v,$.v<100?1:0)} W `),$.s.cid&&(b.push(`${this._t("lbl.click_to_select",{},"click to select")} `),this._hoverNearest={id:s,cid:$.s.cid})}}else v.forEach($=>{const L=T($);L!=null&&(f(L,_($)),b.push(`${d($.name||this._t("lbl.power",{},"Power"))}: ${it(L,L<100?1:0)} W `))});if(i.band){const $=Gt(i.band.min,n),L=Gt(i.band.max,n);$!=null&&L!=null&&b.push(`${this._t("lbl.envelope",{},"Envelope")}: ${it($,$<100?1:0)}\u2013${it(L,L<100?1:0)} W`)}(i.artifacts||[]).forEach($=>{if(n>=$.start_s&&n<=$.end_s){const L=$.detail_key?this._tText($.detail_key,$.detail_params||{},$.detail||""):$.detail||"";b.push(`\u26A0 ${d(ue($.type,(D,B,U)=>this._t(D,B,U)))} : ${d(L)}`)}});const C=!!this._canvasZoom[s];let x=null;if(a.touch?x=C?this._t("lbl.zoom_hint_touch_zoomed",{},"pinch to zoom \xB7 double-tap to reset"):this._t("lbl.zoom_hint_touch",{},"pinch to zoom"):C&&(x=this._t("lbl.zoom_hint",{},"scroll to zoom \xB7 dblclick to reset")),x&&b.push(`${d(x)} `),a.touch){const $=i.ch-i.padB;c.beginPath(),c.arc(p,$,Tt*.41*i.dpr,0,6.2832),c.fillStyle=i.primary,c.fill(),c.strokeStyle="rgba(255,255,255,.92)",c.lineWidth=1.6*i.dpr,c.stroke(),this._gAxisHandle||(this._gAxisHandle={}),this._gAxisHandle[s]=i.xToCss(n)}else this._gAxisHandle&&delete this._gAxisHandle[s];c.restore(),this._showGraphTip(t,e,b,{touch:a.touch,rect:o}),this._syncSpagRowHighlight(this._hoverNearest?this._hoverNearest.cid:null)}_showGraphTip(t,e,s,a={}){const r=this._gtip;if(!r)return;this._gtipLines=s,r.innerHTML=s.join(" "),r.classList.remove("wd-gtip--pinned"),this._gtipPinned=!1,r.style.display="block";const i=r.offsetWidth,o=r.offsetHeight,n=16;let l,c;if(a.touch&&a.rect){const p=a.rect,_=8;p.top-o-_>=6?c=p.top-o-_:p.bottom+_+o<=window.innerHeight-6?c=p.bottom+_:c=e>p.top+p.height*.5?p.top+6:p.bottom-o-6,l=t-i/2}else l=t+n,c=e+n,c+o>window.innerHeight-6&&(c=e-o-n),l+i>window.innerWidth-6&&(l=t-i-n);r.style.left=Math.max(6,Math.min(l,window.innerWidth-i-6))+"px",r.style.top=Math.max(6,Math.min(c,window.innerHeight-o-6))+"px"}_pinGraphTip(){const t=this._gtip;if(!t||t.style.display==="none"||this._gtipPinned)return;this._gtipPinned=!0,t.classList.add("wd-gtip--pinned");const e=this._t("lbl.tap_to_dismiss",{},"tap the chart to dismiss");t.innerHTML=(this._gtipLines||[]).join(" ")+`${d(e)} `}_maybeDismissGraphTip(t){if(!this._gtipPinned)return;const e=t.target;e&&e.closest&&e.closest(".wd-canvas-wrap")||this._unpinGraphTip()}_unpinGraphTip(){this._gtipPinned&&(this._gtipPinned=!1,this._gtip&&(this._gtip.classList.remove("wd-gtip--pinned"),this._gtip.style.display="none"))}_hideGraphTip(){this._hoverRafId&&(cancelAnimationFrame(this._hoverRafId),this._hoverRafId=null,this._hoverPending=null),this._gtipPinned=!1,this._gtip&&(this._gtip.classList.remove("wd-gtip--pinned"),this._gtip.style.display="none"),this._syncSpagRowHighlight(null)}_positionTip(t){const e=t.querySelector(".wd-tip-pop");if(!e)return;e.style.transform="";const s=!e.offsetWidth,a=s?e.style.cssText:"";s&&(e.style.visibility="hidden",e.style.display="block");const r=e.getBoundingClientRect();if(s&&(e.style.cssText=a),!r.width)return;const i=be(t),o=6,n=i.left+o,l=i.right-o-r.width,c=li,l=Math.max(64,Math.floor(n?o:i));e.classList.toggle("wd-drop-up",n),e.style.maxHeight=`${Math.min(220,l)}px`}_syncSpagRowHighlight(t){if(t===this._spagHoverCid)return;this._spagHoverCid=t;const e=this.shadowRoot;e&&e.querySelectorAll("tr[data-cid]").forEach(s=>{s.style.backgroundColor=t&&s.dataset.cid===t?"var(--secondary-background-color,rgba(0,0,0,.06))":""})}_showToast(t,e="success",s={}){this._toastTimer&&clearTimeout(this._toastTimer);const a=s.duration||3500;this._toast={msg:t,cls:`wd-toast-${e}`,actionLabel:s.actionLabel||null,actionToken:s.actionToken||null},this._render(),this._toastTimer=setTimeout(()=>{this._toast=null,this._render()},a)}async _openAutoLabelModal(t){let e=this._opts?this._opts.auto_label_confidence:void 0;if(e==null&&t)try{const r=await this._ws({type:`${w}/get_options`,entry_id:t});if(!this._isActiveEntry(t))return;e=(r&&r.options||{}).auto_label_confidence}catch{}e==null&&(e=(wt.auto_label_confidence||{}).def);const s=Number(e),a=Number.isFinite(s)?+Math.min(.95,Math.max(.5,s)).toFixed(2):.9;this._modal={type:"auto-label",threshold:a},this._render()}_autoLabelToast(t){const e=t&&t.result;return!e||typeof e.labeled!="number"?this._tText("msg.toast_auto_label_complete",{},"Auto-label complete"):t.state==="cancelled"?this._tText("toast.auto_label_cancelled",{count:e.labeled},"Auto-label cancelled after labelling {count} cycles."):this._tText("toast.auto_label_done",{count:e.labeled,skipped:e.skipped||0},"Auto-label complete: {count} cycles labelled, {skipped} left unlabelled.")}_profileOptions(t){return(this._profiles||[]).map(e=>`${d(e.name)} `).join("")}_htmlModal(){const t=this._modal;if(t.type==="cycle-detail")return`${this._htmlCycleModal(t)}
`;if(t.type==="profile-panel")return`${this._htmlProfilePanel(t)}
`;if(t.type==="profile-group")return`${this._htmlProfileGroupModal(t)}
`;if(t.type==="compare-cycles")return`${this._htmlCompareModal(t)}
`;if(t.type==="gear-settings")return`${this._htmlGearModal(t)}
`;if(t.type==="export-select")return`${this._htmlExportSelectModal(t)}
`;if(t.type==="import-wizard")return`${this._htmlImportWizardModal(t)}
`;if(t.type==="history-import")return`${this._htmlHistoryImportModal(t)}
`;let e="";if(t.type==="confirm"){const s=(t.items||[]).map(a=>`${d(a.text)}${a.sub?`${d(a.sub)}
`:""} `).join("");e=`${d(t.title)} ${d(t.message)}
${s?``:""}
${this._t("btn.cancel",{},"Cancel")}
${d(t.okLabel||this._t("btn.confirm",{},"Confirm"))}
`}else if(t.type==="label-cycle")e=`${this._t("modal.label_cycle",{},"Label Cycle")}
${this._t("lbl.select_profile",{},"Select Profile")}
${this._t("lbl.remove_label",{},"- Remove label -")} ${this._t("lbl.create_new_profile",{},"+ Create new profile\u2026")} ${this._profileOptions()}
${this._t("lbl.new_profile_name",{},"New Profile Name")}
${this._t("btn.cancel",{},"Cancel")}
${this._t("btn.apply_label",{},"Apply Label")}
`;else if(t.type==="create-profile"){const s=(this._cycles||[]).slice(0,40);let a=s.map(r=>`${et(r.start_time)} - ${Math.round((r.duration||0)/60)}m - ${d(r.profile_name||this._t("lbl.unlabelled",{},"Unlabelled"))} `).join("");t.prefillCycle&&!s.some(r=>r.id===t.prefillCycle)&&(a=`${d(t.prefillCycleMin?t.prefillCycleMin+"m":"")} - ${d(this._t("lbl.unlabelled",{},"Unlabelled"))} `+a),e=`${this._t("modal.create_profile",{},"Create Profile")}
${this._t("lbl.profile_name",{},"Profile Name")}
${this._t("lbl.ref_cycle",{},"Reference Cycle (optional)")} ${this._t("lbl.ref_cycle_none",{},"None")} ${a}
${this._t("btn.cancel",{},"Cancel")}
${this._t("btn.create",{},"Create")}
`}else if(t.type==="create-phase")e=`${this._t("modal.new_phase",{},"New Phase")}
${this._t("lbl.phase_name",{},"Phase Name")}
${this._t("lbl.description",{},"Description")}
${this._t("btn.cancel",{},"Cancel")}
${this._t("btn.create",{},"Create")}
`;else if(t.type==="edit-phase"){const s=t.isDefault?`${this._t("msg.edit_builtin_phase",{},"This is a built-in phase. Saving creates a custom copy; delete the copy to restore the original.")}
`:"";e=`${this._t("modal.edit_phase",{},"Edit Phase")} ${t.isDefault?`${this._t("badge.built_in_tag",{},"built-in")} `:""}
${s}
${this._t("lbl.phase_name",{},"Phase Name")}
${this._t("lbl.description",{},"Description")}
${this._t("btn.cancel",{},"Cancel")}
${this._t("btn.save",{},"Save")}
`}else if(t.type==="process-recording")e=`${this._t("modal.process_recording",{},"Process Recording")}
${this._t("lbl.save_mode",{},"Save Mode")} ${this._t("lbl.mode_new_profile",{},"Create New Profile")} ${this._t("lbl.mode_existing_profile",{},"Add to Existing Profile")}
${this._t("btn.cancel",{},"Cancel")}
${this._t("btn.process_recording",{},"Save Recording")}
`;else if(t.type==="correct-feedback")e=`${this._t("modal.correct_feedback",{},"Correct Feedback")}
${this._t("msg.feedback_detected",{name:vt(`${d(t.detectedProfile)} `)},"WashData detected: {name}")}
${this._t("lbl.correct_profile",{},"Correct Profile")} ${this._profileOptions()}
${this._t("lbl.correct_duration",{},"Correct Duration (min, optional)")}
${this._t("btn.cancel",{},"Cancel")}
${this._t("btn.submit_correction",{},"Submit Correction")}
`;else if(t.type==="auto-label")e=`${this._t("modal.auto_label",{},"Auto-Label Cycles")}
${this._t("msg.auto_label_intro",{},"Assign profiles to unlabelled cycles whose match confidence clears the threshold.")}
${this._t("lbl.confidence_threshold",{},"Confidence threshold")}
${this._t("btn.cancel",{},"Cancel")}
${this._t("btn.run_auto_label",{},"Run Auto-Label")}
`;else if(t.type==="merge-cycles")e=`${this._t("modal.merge_cycles",{n:t.ids.length},`Merge ${t.ids.length} Cycles`)}
${this._t("msg.merge_intro",{},"The selected cycles are combined into one (chronological order; gaps filled with 0 W). Pick the resulting profile.")}
${this._t("lbl.resulting_profile",{},"Resulting profile")}
${this._t("lbl.unlabelled_paren",{},"(unlabelled)")} ${this._t("lbl.create_new_profile",{},"+ Create new profile\u2026")} ${this._profileOptions()}
${this._t("lbl.new_profile_name",{},"New profile name")}
${this._t("btn.cancel",{},"Cancel")}
${this._t("btn.merge",{},"Merge")}
`;else if(t.type==="bulk-relabel")e=`${this._t("modal.relabel_cycles",{count:t.ids.length},`Relabel ${t.ids.length} cycles`)}
${this._t("lbl.select_profile",{},"Select Profile")}
${this._t("lbl.remove_label",{},"- Remove label -")} ${this._profileOptions()}
${this._t("btn.cancel",{},"Cancel")}
${this._t("btn.apply_label",{},"Apply Label")}
`;else if(t.type==="store-import"){const s=t.mode!=="merge",a=`${this._t("lbl.select_profile",{},"Select Profile")} ${this._profileOptions()} `;e=`${this._t("modal.store_import",{},"Import reference cycle")}
${this._t("store.import_new",{},"New profile")}
${this._t("store.import_merge",{},"Merge into existing")}
${s?`${this._t("lbl.new_profile_name",{},"New Profile Name")}
`:`${this._t("store.merge_target",{},"Merge into profile")} ${a}
`}
${this._t("btn.cancel",{},"Cancel")}
${this._busy.has("store-import")?' ':""}${this._t("btn.import",{},"Import")}
`}else if(t.type==="store-share"){const s=Array.isArray(t.profiles)?t.profiles:[],a=[],r=new Set,i=l=>{const c=(l||"").toLowerCase();l&&!r.has(c)&&(r.add(c),a.push(l))};i(t.program),s.forEach(l=>i(l.program));const o=t.profiles==null?` ${this._t("msg.loading",{},"Loading\u2026")} `:"",n=a.length?a.map(l=>`${d(l)} `).join(""):t.storeUnreachable?' ':`${this._t("msg.no_profiles_option",{},"(no profiles yet - add one)")} `;e=`${this._t("modal.store_share",{},"Share to community store")}
${this._t("msg.store_share_intro",{},"Upload this reference cycle so others with the same appliance can use it. It is reviewed before appearing publicly.")}
${t.storeUnreachable?this._htmlStoreUnreachable():""}
${this._t("lbl.profile",{},"Profile")}${o}
${n}
+
${this._t("store.description",{},"Description (optional)")}
${this._t("btn.cancel",{},"Cancel")}
${this._busy.has("store-share")?' ':""}${this._t("btn.share",{},"Share")}
`}else t.type==="store-share-device"&&(e=this._htmlShareDeviceModal(t));return``}_htmlShareDeviceModal(t){const e=this._shareableByProgram(),s=this._busy.has("store-share-device"),a=t.selected||new Set,r=!!t.consented,i=e.reduce((u,b)=>u+b.cycles.filter(v=>a.has(v.id)).length,0),o=e.some(u=>!u.noCycles);let n;t.loading?n=`\u23F3
${this._t("msg.loading",{},"Loading\u2026")}
`:e.length?n=e.map(u=>{if(u.noCycles)return`
${d(u.program)}
${this._t("msg.share_profile_no_cycles",{},"No reference cycles \u2013 mark a cycle as \u2B50 in the Cycles tab to include this profile")}
`;const b=u.cycles.every($=>a.has($.id)),v=!b&&u.cycles.some($=>a.has($.id)),T=u.cycles.map($=>{const L=$.start_time?et($.start_time):"",D=$.duration!=null?ht($.duration):"";return`
${d(L)} \xB7 ${d(D)} \u2B50
`}).join(""),x=(this._sharePhasePrograms||[]).includes(u.program)?`
${this._t("lbl.include_phase_map",{},"Include phase map")}
`:"";return``}).join(""):n=`\u{1F4E4}
${this._t("msg.share_device_none",{},"No shareable cycles yet. Mark a recorded or hand-picked cycle as a reference cycle (\u2B50) in the Cycles tab first.")}
`;const l=(this._opts.store_brand||"").trim(),c=(this._opts.store_model||"").trim(),p=`
${this._t("lbl.include_settings",{},"Include detection & matching settings")}
${ot(this._t("msg.include_settings_hint",{},"Share this device's detection and matching thresholds (not notifications, entities or energy price). Others choose whether to apply them."))}
`,_=`
${this._t("msg.share_guidelines_title",{},"Before you share")}
${this._t("msg.share_guideline_naming",{},"Name each profile exactly as shown on the appliance dial or display (e.g. 'Cotton 40', 'Eco 60').")}
${this._t("msg.share_guideline_quality",{},"Only share cycles that finished normally: no interruptions, door openings or power blips.")}
${this._t("msg.share_guideline_review",{},"Your upload starts as pending and appears publicly once enough community members confirm it.")}
`,f=o?`
${this._t("lbl.share_consent",{},"I confirm these cycles ran to normal completion without interruption")}
`:"";return`${this._t("modal.store_share_device",{},"Share this device")}
${this._t("msg.store_share_device_intro",{brand:l,model:c},"Upload {brand} {model} with the reference cycles you select. Others with the same appliance can adopt your programs. Entries are reviewed before appearing publicly.")}
${_}
${n}
${o?p:""}
${f}
${this._t("btn.cancel",{},"Cancel")}
${s?' ':""}${this._t("btn.share_n",{n:i},`Share ${i} cycle(s)`)}
`}_wizCatOrder(){return["profiles","real_cycles","reference_cycles","backfill_cycles","custom_phases","profile_groups","settings","ml_models","feedback","suggestions","maintenance_log","history_logs","lifetime_stats"]}_wizCycleCats(){return["real_cycles","reference_cycles","backfill_cycles"]}_wizIdSet(t,e){return e==="real_cycles"?t.realIds:e==="backfill_cycles"?t.bfIds:t.refIds}_wizEmptySel(){return{cats:new Set,profiles:new Set,realIds:new Set,refIds:new Set,bfIds:new Set}}_wizAnySel(t){return!!(t&&(t.profiles.size||t.realIds.size||t.refIds.size||t.bfIds.size||t.cats.size))}_wizCatLabel(t){return{profiles:this._t("lbl.cat_profiles",{},"Profiles (programs)"),real_cycles:this._t("lbl.cat_real_cycles",{},"Cycles (run history)"),reference_cycles:this._t("lbl.cat_reference_cycles",{},"Reference cycles (imported)"),backfill_cycles:this._t("lbl.cat_backfill_cycles",{},"Cycles found in imported power history"),custom_phases:this._t("lbl.cat_custom_phases",{},"Custom phases"),profile_groups:this._t("lbl.cat_profile_groups",{},"Profile groups"),settings:this._t("lbl.cat_settings",{},"Detection & matching settings"),ml_models:this._t("lbl.cat_ml_models",{},"ML models"),feedback:this._t("lbl.cat_feedback",{},"Feedback & review labels"),suggestions:this._t("lbl.cat_suggestions",{},"Suggestions"),maintenance_log:this._t("lbl.cat_maintenance_log",{},"Maintenance log"),history_logs:this._t("lbl.cat_history_logs",{},"History & change logs"),lifetime_stats:this._t("lbl.cat_lifetime_stats",{},"Lifetime totals")}[t]||t}_wizInitSel(t,e){const s=this._wizEmptySel(),a=t&&t.categories||{},r=i=>!!(i&&i.present&&(!e||i.importable!==!1));return r(a.profiles)&&(a.profiles.items||[]).forEach(i=>s.profiles.add(i.name)),this._wizCycleCats().forEach(i=>{const o=this._wizIdSet(s,i);r(a[i])&&(a[i].groups||[]).forEach(n=>n.cycles.forEach(l=>{l.id!=null&&o.add(String(l.id))}))}),Object.keys(a).forEach(i=>{i==="profiles"||this._wizCycleCats().includes(i)||r(a[i])&&s.cats.add(i)}),s}_wizSelectionPayload(t){const e=t.sel,s=[];e.profiles.size&&s.push("profiles"),e.realIds.size&&s.push("real_cycles"),e.refIds.size&&s.push("reference_cycles"),e.bfIds.size&&s.push("backfill_cycles"),e.cats.forEach(r=>s.push(r));const a={categories:s};return e.profiles.size&&(a.profiles=Array.from(e.profiles)),e.realIds.size&&(a.real_cycle_ids=Array.from(e.realIds)),e.refIds.size&&(a.reference_cycle_ids=Array.from(e.refIds)),e.bfIds.size&&(a.backfill_cycle_ids=Array.from(e.bfIds)),a}_wizGroupIds(t,e,s){const r=(((t.categories||{})[e]||{}).groups||[]).find(i=>i.profile===s);return r?r.cycles.filter(i=>i.id!=null).map(i=>String(i.id)):[]}_wizCatState(t,e,s){const a=t.sel,r=(s.categories||{})[e]||{};if(e==="profiles"){const o=r.items||[],n=o.length,l=o.filter(c=>a.profiles.has(c.name)).length;return{sel:l,total:n,state:l===0?"none":l===n?"all":"some"}}if(this._wizCycleCats().includes(e)){const o=this._wizIdSet(a,e),n=r.count||0;let l=0;return(r.groups||[]).forEach(c=>c.cycles.forEach(p=>{o.has(String(p.id))&&l++})),{sel:l,total:n,state:l===0?"none":l===n?"all":"some"}}const i=a.cats.has(e);return{sel:i?1:0,total:1,state:i?"all":"none"}}_htmlSelectionTree(t,e,s={}){const a=e&&e.categories||{},r=new Set;s.conflicts&&(a.profiles&&a.profiles.items||[]).forEach(p=>{p.conflict&&r.add(p.name)});const i=this._wizCatOrder().filter(p=>a[p]&&a[p].present);let o=!1,n=!0;i.forEach(p=>{if(s.importableOnly&&a[p].importable===!1)return;const _=this._wizCatState(t,p,e);_.state!=="none"&&(o=!0),_.state!=="all"&&(n=!1)});const l=`
${this._t("btn.select_all",{},"Select all")}
`,c=i.map(p=>{const _=a[p],f=s.importableOnly&&_.importable===!1,u=this._wizCatState(t,p,e),v=p==="profiles"||this._wizCycleCats().includes(p)?`${u.sel}/${_.count}`:"",T=`
${d(this._wizCatLabel(p))}${f?` ${this._t("badge.not_importable",{},"n/a here")} `:""}
${v}
`;let C="";if(!f&&p==="profiles")C=`${(_.items||[]).map(x=>`
${d(x.name)} \xB7 ${(x.real_cycles||0)+(x.reference_cycles||0)+(x.backfill_cycles||0)} ${this._t("lbl.cycles_short",{},"cycles")}${r.has(x.name)?` ${this._t("badge.exists",{},"exists")} `:""}
`).join("")}
`;else if(!f&&this._wizCycleCats().includes(p)){const x=this._wizIdSet(t.sel,p);C=`${(_.groups||[]).map($=>{const L=$.cycles.map(O=>String(O.id)),D=L.filter(O=>x.has(O)).length,B=D===L.length&&L.length>0,U=D>0&&!B,z=`${p}:${$.profile}`,M=t.expanded&&t.expanded.has(z),A=M?$.cycles.map(O=>`
${d(O.date?et(O.date):String(O.id))}${O.duration!=null?" \xB7 "+ht(O.duration):""}
`).join(""):"";return`
`}).join("")}
`}return`${T}${C}
`}).join("");return`${l}${c}
`}_htmlExportSelectModal(t){const e=this._busy.has("export-select");let s;t.loading||!t.inventory?s=`\u23F3
${this._t("msg.loading",{},"Loading\u2026")}
`:s=this._htmlSelectionTree(t,{categories:t.inventory},{importableOnly:!1});const a=this._wizAnySel(t.sel);return`${this._t("modal.export_select",{},"Export - choose data")}
${this._t("msg.export_select_intro",{},"Tick what to include. Profiles without their cycles still export a matchable program.")}
${s}
${this._t("btn.cancel",{},"Cancel")}
${e?' ':""}${this._t("btn.download_export",{},"Download export")}
`}_htmlImportWizardModal(t){const e=this._busy.has("import-wizard"),s=`${this._t("modal.import_wizard",{},"Import - choose data")} `;if(t.step==="input"||t.step==="analyze"){const T=t.step==="analyze";return`${s}
${this._t("msg.import_analyze_hint",{},"Load an exported file (or paste its JSON). WashData shows what can be imported before anything changes.")}
${this._t("lbl.load_from_file",{},"Load from file")}
${this._t("lbl.json_data",{},"JSON Data")}
${t.error?`${d(t.error)}
`:""}
${this._t("btn.cancel",{},"Cancel")}
${T?' '+this._t("msg.analyzing",{},"Analyzing\u2026"):this._t("btn.analyze_import",{},"Analyze file")}
`}const a=t.manifest||{},r=a.device_type_match===!1,i=a.source_device_type||"?",o=a.local_device_type||"?",n=r?`
${this._t("msg.device_type_mismatch_warn",{src:i,local:o},"This export is from a different appliance type ({src} vs {local}). Programs and cycles can still be imported as reference data, but not device settings or real history.")}
`:"",l=this._htmlSelectionTree(t,a,{importableOnly:!0,conflicts:!0}),c=`${this._t("lbl.import_mode",{},"How to combine")}
${this._t("lbl.mode_merge",{},"Merge (keep mine)")}
${this._t("lbl.mode_replace",{},"Replace selected")}
${t.mode==="replace"?this._t("msg.replace_warn",{},"Each ticked category is wiped and replaced from the file. Unticked categories are left untouched.")+" "+this._t("msg.replace_undo_hint",{},"A restore point is saved first, so you can undo this import from Export / Import."):this._t("msg.merge_hint",{},"Imported items are added; nothing local is lost. Name clashes are resolved below.")}
`,p=a.real_history_allowed!==!1,_=`${this._t("lbl.cycle_destination",{},"Where should imported cycles go?")}
${this._t("lbl.dest_reference",{},"Reference (shape only)")}
${this._t("lbl.dest_real_history",{},"Real history (counts in stats)")}
${t.cycleDest==="real_history"?this._t("msg.dest_real_history_hint",{},"Imported cycles count as this device's own history and statistics. Use this to move an appliance to a new install."):this._t("msg.dest_reference_hint",{},"Imported cycles only improve program matching and never affect usage/energy statistics.")}
`;let f="";const b=(a.categories&&a.categories.profiles&&a.categories.profiles.items||[]).filter(T=>T.conflict&&t.sel.profiles.has(T.name));t.mode==="merge"&&b.length&&(f=`${this._t("lbl.conflict_resolution",{},"Name clashes")}
${b.map(T=>`
${d(T.name)}
${this._t("lbl.conflict_import_copy",{},"Import as copy")}
${this._t("lbl.conflict_keep_mine",{},"Keep mine")}
${this._t("lbl.conflict_overwrite",{},"Overwrite")}
`).join("")}
`);const v=this._wizAnySel(t.sel);return`${s}
${n}
${l}
${c}
${_}
${f}
${this._t("btn.back",{},"Back")}
${e?' ':""}${this._t("btn.import_selected",{},"Import selected")}
`}_histSkipReason(t){return{idle:this._t("lbl.hist_skip_idle",{},"nothing running"),sparse:this._t("lbl.hist_skip_sparse",{},"readings too far apart"),too_few_samples:this._t("lbl.hist_skip_short",{},"too few readings"),too_long:this._t("lbl.hist_skip_long",{},"no break long enough to split on")}[t]||t||""}_histSegReason(t){return{shorter_than_minimum:this._t("lbl.hist_reason_short",{},"shorter than this appliance's shortest real cycle"),no_clean_end:this._t("lbl.hist_reason_no_end",{},"never ended cleanly"),already_recorded:this._t("lbl.hist_reason_already_recorded",{},"overlaps a cycle WashData already recorded")}[t]||""}_htmlHistoryImportModal(t){const e=`${this._t("modal.history_import",{},"Import power history")} `,s=t.error?`${d(t.error)}
`:"";if(t.step==="input"){const x=this._busy.has("hist-import"),$=x?"disabled":"";return`${e}
${this._t("msg.hist_input_hint",{},"Upload a CSV from the History panel (entity, state, last changed), or let WashData read the sensor's history directly. Live detection runs over it and you choose which cycles to keep.")}
${this._t("lbl.load_from_file",{},"Load from file")}
${this._t("lbl.hist_csv_data",{},"CSV data")}
${s}
${this._t("btn.cancel",{},"Cancel")}
${x?' ':""}${this._t("btn.hist_scan",{},"Scan for cycles")}
`}if(t.step==="scan"){const x=t.scanTaskId?(this._tasks||{})[t.scanTaskId]:null,$=x&&x.total>0?Math.round(x.done/x.total*100):null;return`${e}
${this._t("msg.hist_scanning",{},"Running your history through the detector in the background. You can close this dialog and come back.")}
${$==null?this._t("status.preparing",{},"Preparing\u2026"):`${$}%`}
${s}
${this._t("btn.cancel",{},"Cancel")}
`}if(t.step==="done"){const x=t.done||{},$=[this._tText("msg.hist_imported_count",{n:x.imported||0},`${x.imported||0} cycles imported.`),x.duplicates?this._tText("msg.hist_duplicates",{n:x.duplicates},`${x.duplicates} were already imported and were skipped.`):"",x.capped?this._t("msg.hist_capped",{},"The per-device limit for imported cycles was reached; the rest were not stored."):""].filter(Boolean);return`${e}
${$.map(L=>`${d(L)}
`).join("")}
${this._t("msg.hist_next_step",{},"They are in your Cycles list, tagged as imported history. Open one and use Label to name its program.")}
${this._t("btn.close",{},"Close")}
${this._t("btn.hist_goto_cycles",{},"Show me the cycles")}
`}const a=t.result||{},r=a.segments||[],i=a.parse||{},o=this._busy.has("hist-apply"),n=t.accept||new Set,l=[];i.rows_total&&l.push(this._tText("msg.hist_rows_read",{n:i.rows_total},`${i.rows_total} readings read`)),i.first&&i.last&&l.push(`${et(i.first)} \u2013 ${et(i.last)}`),i.breaks&&l.push(this._tText("msg.hist_breaks",{n:i.breaks},`${i.breaks} gaps where the sensor was unavailable`)),i.rows_other_entity&&l.push(this._tText("msg.hist_other_entity",{n:i.rows_other_entity},`${i.rows_other_entity} readings for other entities ignored`)),i.truncated&&l.push(this._tText("msg.hist_truncated",{n:i.rows_total||0},`stopped at the ${i.rows_total||0}-reading limit, so the rest of the history was not scanned`)),i.entity_substituted_from&&l.push(this._tText("msg.hist_entity_substituted",{used:i.entity_id||"?",wanted:i.entity_substituted_from},`read ${i.entity_id||"?"} (this device is configured for ${i.entity_substituted_from})`));const c={looks_like_kw:this._tText("msg.hist_warn_kw",{w:i.peak_w},`The highest reading is only ${i.peak_w}. This sensor seems to report kilowatts; WashData needs watts, so no cycles can be detected.`),naive_timestamps:this._tText("msg.hist_warn_naive_time",{n:i.rows_naive_time||0},`${i.rows_naive_time||0} timestamps have no time zone and were read as UTC. If the file uses local time, cycles are shifted by your UTC offset and readings near a DST change may be dropped.`)},p=(i.warnings||[]).map(x=>c[x]).filter(Boolean).map(x=>`\u26A0 ${d(x)}
`).join(""),_=a.skipped||[],f={};_.forEach(x=>{f[x.reason]=(f[x.reason]||0)+1});const u=Object.entries(f).map(([x,$])=>`${$} \xD7 ${this._histSkipReason(x)}`).join(", "),b=a.settings||{},v=b.min_power!=null?this._tText("msg.hist_settings_used",{w:b.min_power,s:b.off_delay},`Detected using this device's current settings (minimum power ${b.min_power} W, off delay ${b.off_delay} s).`):"";if(!r.length)return`${e}
${this._t("msg.hist_none_found",{},"No cycles could be detected in that history.")}
${p}
${l.length?`${d(l.join(" \xB7 "))}
`:""}
${u?`${this._t("msg.hist_skipped_spans",{},"Skipped stretches")}: ${d(u)}
`:""}
${v?`${d(v)}
`:""}
${this._t("btn.back",{},"Back")}
${this._t("btn.close",{},"Close")}
`;const T=r.map(x=>{const $=n.has(x.index),L=this._histSegReason(x.reason);return`
${d(et(x.start_time))}
${d(ht(x.duration_s))}${x.banked_tail_s?" \u2702":""}
${x.energy_wh!=null?d(it(x.energy_wh/1e3,2))+" kWh":"\u2013"}
${d(String(Math.round(x.peak_w)))} W
${L?`\u26A0 ${d(L)} `:`${d(this._t("lbl.hist_looks_complete",{},"complete"))} `}
`}).join(""),C=r.every(x=>n.has(x.index));return`${e}
${this._t("msg.hist_found",{n:r.length},`Found ${r.length} cycles. Untick anything that does not look like a real run - nothing is stored until you import.`)}
${p}
${l.length?`${d(l.join(" \xB7 "))}
`:""}
${u?`${this._t("msg.hist_skipped_spans",{},"Skipped stretches")}: ${d(u)}
`:""}
${v?`${d(v)}
`:""}
${C?this._t("btn.select_none",{},"Select none"):this._t("btn.select_all",{},"Select all")}
${this._t("lbl.date",{},"Date")}
${this._t("lbl.duration",{},"Duration")}
${this._t("lbl.energy",{},"Energy")}
${this._t("lbl.peak_power_short",{},"Peak")}
${this._t("lbl.shape",{},"Shape")}
${this._t("lbl.notes",{},"Notes")}
${T}
${a.capped?`${this._t("msg.hist_scan_capped",{n:a.found},`Only the first candidates are shown (${a.found} were found).`)}
`:""}
${s}
${this._t("btn.back",{},"Back")}
${o?' ':""}${this._t("btn.hist_import_n",{n:n.size},`Import ${n.size} cycles`)}
`}_drawHistorySparklines(){const t=this.shadowRoot;if(!t)return;const e=this._modal,s=e&&e.result&&e.result.segments||[],a={};s.forEach(r=>{a[String(r.index)]=r.curve||[]}),t.querySelectorAll("canvas[data-hist-spark]").forEach(r=>{this._paintSparkline(r,a[r.dataset.histSpark]||[])})}_htmlCycleModal(t){if(!t.loaded)return`${this._t("modal.cycle",{},"Cycle")} \u23F3
${this._t("msg.loading_curve",{},"Loading curve\u2026")}
${this._t("btn.close",{},"Close")}
`;const e=t.curve||{},s=!!e.is_reference,a=e.labelable!==!1,r=e.editable!==!1,i=e.cycle_origin==="backfill",o=e.full_duration_s||e.duration||0,n=e.energy_kwh!=null?e.energy_kwh:null,l=t.ml||null,c=`
${ht(e.duration||o)}
${this._t("lbl.duration",{},"Duration")}
${gt(n)}
${this._t("lbl.energy",{},"Energy")}
${d(e.profile_name||this._t("lbl.unlabelled",{},"Unlabelled"))}
${this._t("lbl.profile",{},"Profile")}
${d(e.status?this._cycleStatusLabel(e.status):"-")}
${this._t("lbl.status",{},"Status")}
`,p=l&&l.ml_review||{},f=(this._feedbacks||[]).some(I=>I.cycle_id===t.cycleId),u=f&&t.mode!=="review"?` \u25CF `:"",b=this._canEdit()&&r?`
${this._t("btn.inspect",{},"Inspect")}
${this._t("btn.trim",{},"Trim")}
${this._t("btn.split",{},"Split")}
${this._t("btn.review",{},"Review")}${u}
`:s?`\u{1F4E5} ${i?this._t("msg.imported_history_readonly",{},"Found in imported power history. It helps matching but is not counted in your statistics and cannot be trimmed or split. Label it to name the program."):this._t("msg.imported_readonly",{},"Imported from the community store. Used for reference and matching, not counted in your stats, and cannot be edited.")}
`:"",v=this._canEdit()?(this._feedbacks||[]).find(I=>I.cycle_id===t.cycleId):null,T=v?v.detected_profile||v.profile_name||this._t("lbl.unknown",{},"Unknown"):"",C=v?`
\u26A0 ${this._t("msg.pending_feedback",{},"Pending detection feedback")}
${this._t("msg.unsure_detected_prefix",{},"WashData is unsure it detected")} ${d(T)} ${v.confidence!=null?` (${this._t("lbl.confidence",{},"confidence").toLowerCase()} ${(v.confidence*100).toFixed(0)}%)`:""}. ${this._t("msg.feedback_prompt",{},"Confirm it was right, correct the program, or ignore.")} ${this._t("msg.feedback_relabel_hint",{},"Re-labelling this cycle resolves it too.")}
${this._t("btn.confirm",{},"Confirm")}
${this._t("btn.correct",{},"Correct\u2026")}
${this._t("btn.ignore",{},"Ignore")}
`:"";let x="";if(t.mode==="view"){const I=!!(l&&l.ml_review&&l.ml_review.golden),G=this._canEdit()&&this._onlineEnabled()&&this._storeConnected&&I?`${this._t("btn.share_to_store",{},"Share to store")} `:"",W=this._canEdit()?`${this._t("btn.delete",{},"Delete")} ${a?`
${this._t("btn.label",{},"Label")} `:""}`:"";x=`${C}
${this._t("btn.close",{},"Close")}
${G}
${W}
`}else if(t.mode==="trim"){const I=this._busy.has("cyc-trim-apply"),R=t.timeMode||"s",G=R==="clock"?this._offsetToClock(t.trim.start):Math.round(t.trim.start),W=R==="clock"?this._offsetToClock(t.trim.end):Math.round(t.trim.end),J=R==="clock"?"time":"number",Y=R==="clock"?'step="1"':`min="0" max="${Math.ceil(o)}" step="1"`,nt=R==="clock"?"":" "+this._t("lbl.unit_s",{},"(s)");x=`${this._t("msg.trim_intro",{},"Drag the red handles, or enter values. Everything outside the window is removed.")}
${this._t("lbl.input",{},"Input:")}
${this._t("lbl.seconds_from_start",{},"Seconds from start")}
${this._t("lbl.clock_time",{},"Clock time")}
${this._t("btn.close",{},"Close")}
${this._t("btn.reset",{},"Reset")}
${I?' '+this._t("status.trimming",{},"Trimming\u2026"):this._t("btn.apply_trim",{},"Apply Trim")}
`}else if(t.mode==="split"){const I=this._busy.has("cyc-split-apply"),R=(t.split.offsets||[]).slice().sort((J,Y)=>J-Y),G=[0,...R,o],W=G.slice(0,-1).map((J,Y)=>{const nt=G[Y+1];return`
${ht(J)} \u2013 ${ht(nt)}
${this._t("lbl.unlabelled_paren",{},"(unlabelled)")} ${this._profileOptions(t.split.profiles[Y])}
`}).join("");x=`${this._t("msg.split_intro",{},"Click the graph to add or remove a split point, or auto-detect by idle gaps. Each part can get its own profile.")}
${R.length?W:`
${this._t("msg.no_split_points",{},"No split points yet.")}
`}
${this._t("btn.close",{},"Close")}
${I?' '+this._t("status.splitting",{},"Splitting\u2026"):this._t("btn.apply_split",{},"Apply Split")}
`}else if(t.mode==="review"){const I=l&&l.ml_review||{},R=this._busy.has("cyc-review-save"),G=(m,g)=>`${g} `,J=[["late_start",this._t("tag.late_start",{},"Late start")],["early_end",this._t("tag.early_end",{},"Early end")],["merged",this._t("tag.merged",{},"Merged cycles")],["split",this._t("tag.split",{},"Split cycle")],["noise",this._t("tag.noise",{},"Noise")],["wrong_profile",this._t("tag.wrong_profile",{},"Wrong profile")],["sensor_gap",this._t("tag.sensor_gap",{},"Sensor gap")]].map(([m,g])=>` ${g} `).join(""),Y=I.reviewed_at?`${this._t("lbl.reviewed_on",{date:Oe(I.reviewed_at)},"reviewed {date}")} `:"",nt=ot(this._t("msg.review_profile_tip",{},"The program this cycle is labelled as. If it is wrong, correct it here; labels teach matching.")),_t=ot(this._t("msg.review_quality_tip",{},"How clean this cycle is. Good: a textbook run of this program. Bad: detected, but noisy or unusual. Unusable: mis-detected (merged, cut short or spurious). Affects the health score and which cycles train the model.")),ct=ot(this._t("msg.review_recorded_tip",{},"Mark this as a hand-picked reference cycle for its program, like a manually recorded cycle. Reference cycles are always kept, seed the matching template, and are never removed by cleanup.")),dt=ot(this._t("msg.review_tags_tip",{},"Optional flags for what went wrong, so training and cleanup can allow for it.")),ut=ot(this._t("msg.review_notes_tip",{},"Free-text notes for your own reference. Not used by matching or training."));x=`
${C}
${this._t("msg.review_confirm_help",{},"Was this cycle detected correctly? Confirming cycles improves matching for your machine. A quick Good/Bad is enough.")}
${this._t("lbl.profile",{},"Profile")}${nt}
${this._t("lbl.unlabelled_paren",{},"(unlabelled)")} ${this._profileOptions(t.reviewLabel!==void 0?t.reviewLabel:e.profile_name)}
${this._t("lbl.quality",{},"Quality")}${_t}
${G("","-")}${G("good",this._t("quality.good",{},"Good"))}${G("bad",this._t("quality.bad",{},"Bad"))}${G("unusable",this._t("quality.unusable",{},"Unusable"))}
${this._t("badge.golden_cycle",{},"Recorded reference cycle")}${ct}
${Y}
${this._t("lbl.compare_profiles",{},"Compare with profiles")}${ot(this._t("msg.compare_profiles_tip",{},"Overlay other profiles on the chart to see which fits this cycle best."))}
${(this._profiles||[]).map(m=>{const g=(t.overlays||[]).includes(m.name),k=g?` `:"";return` ${k}${d(m.name)} `}).join("")||`${this._t("msg.no_profiles_compare",{},"No profiles to compare.")} `}
${this._t("lbl.tags",{},"Tags")}${dt}
${J}
${this._t("lbl.notes",{},"Notes")}${ut}
${this._t("btn.close",{},"Close")}
${R?' '+this._t("status.saving",{},"Saving\u2026"):this._t("btn.save_review",{},"Save Review")}
`}let $="";const L=t.mode==="view"||t.mode==="review"?e.artifacts||[]:[];if(L.length){const I=L.map(R=>{const G=R.detail_key?this._tText(R.detail_key,R.detail_params||{},R.detail||""):R.detail||"";return`${d(ue(R.type,(W,J,Y)=>this._t(W,J,Y)))} ${this._t("lbl.at",{},"at")} ${$t(R.start_s)}\u2013${$t(R.end_s)} \u2014 ${d(G)} `}).join("");$=`
\u26A0 ${this._t("msg.artifact_header",{n:L.length},`${L.length} anomal${L.length>1?"ies":"y"} detected during this cycle`)}
${this._t("msg.artifact_footer",{},"Highlighted on the graph above. These are transient artifacts (e.g. the door opened mid-cycle), not necessarily problems.")}
`}let D="";const B=t.mode==="view"||t.mode==="review"?e.restart_gaps||[]:[];if(B.length){const I=B.map(R=>{const G=Math.round((R.gap_seconds||0)/60),W=G>=1?`${G}m`:`${Math.round(R.gap_seconds||0)}s`,J=R.match_confidence!=null?` \xB7 ${this._t("lbl.pct_match_confidence",{pct:Math.round(R.match_confidence*100)},`${Math.round(R.match_confidence*100)}% match confidence`)}`:"",Y=R.profile?` (${d(R.profile)})`:"";return`${this._t("msg.restart_gap_item",{dur:W},`${W} gap`)}: ${this._t("lbl.ha_restarted",{},"HA restarted")}${Y}${J} `}).join("");D=`
\u21BB ${this._t("msg.restart_gap_header",{n:B.length},`${B.length} HA restart gap${B.length>1?"s":""} during this cycle`)}
${this._t("msg.restart_gap_footer",{},"Highlighted on the graph. Power data is missing for these intervals; matching used only real readings.")}
`}let U="";if(e.decimated){const I=(e.samples||[]).length,R=e.sample_count||I;U=`${this._t("msg.samples_decimated",{shown:I,total:R},`Showing ${I} of ${R} samples (thinned for display; peaks kept). A wide gap here is thinning, not missing data.`)}
`}const z=this._cycleProfileOverlays(t),M=I=>I.dash?`background:repeating-linear-gradient(90deg,${I.stroke} 0 5px,transparent 5px 8px)`:`background:${I.stroke}`,A=this._cycleContextSeries(t),O=A?[...z,A.series]:z,K=O.length?`
${this._t("lbl.power",{},"Power")}
${O.map(I=>` ${d(I.name)} `).join("")}
`:"";return`${this._t("lbl.cycle",{},"Cycle")} \xB7 ${d(et(e.start_time))}
${c}${b}
${this._htmlCycleContextBar(t,e)}
${K}
${U}
${$}
${D}
${x}`}_htmlCycleContextBar(t,e){if(t.mode!=="view"&&t.mode!=="review"||e.is_reference||!(e.samples||[]).length)return"";const s=t.ctx||{minutes:this._cycleContextMinutes(t.entryId),loading:!1,data:null},a=Ft.map(n=>`${n?this._t("lbl.cycle_context_minutes",{min:n},`${n} min`):this._t("lbl.cycle_context_off",{},"Off")} `).join("");let r="",i="";s.minutes&&s.loading?(r="loading",i=this._t("msg.cycle_context_loading",{},"Loading recorder history\u2026")):s.minutes&&s.data&&this._cycleContextSeries(t)?(r="shown",i=this._t("msg.cycle_context_note",{},"Grey: the power sensor's recorder history around this cycle. It is not part of the cycle and does not affect duration, energy or matching.")):s.minutes&&s.data&&(r="none",i=this._t("msg.cycle_context_none",{},"The recorder holds no readings around this cycle (Home Assistant keeps 10 days of history by default)."));const o=ot(this._tText("msg.cycle_context_tip",{},"Display only: shows the power sensor's recorder history around this cycle, if still kept. To add failed start attempts to future cycles, use Curve Pre-roll (Settings > Detection > Cycle Start), which changes stored durations."));return`
${this._t("lbl.cycle_context",{},"Recorder history around the cycle")}
${a} ${o}
${i?`${i} `:""}
`}_htmlProfilePanel(t){const e=this._canEdit();t.tab==="danger"&&!e&&(t.tab="stats");const s=[["stats",this._t("tab.pp_overview",{},"Overview")],["phases",this._t("tab.pp_phases",{},"Phases")],["cleanup",this._t("tab.pp_cleanup",{},"Cleanup")]];e&&s.push(["danger",this._t("tab.pp_manage",{},"Manage")]);const a=s.map(([o,n])=>`${n} `).join("");let r="";if(!t.loaded)r=`\u23F3
${this._t("msg.loading",{},"Loading\u2026")}
`;else if(t.tab==="stats"){const o=t.stats||{},n=t.env||{},l=this._hass&&this._hass.config&&this._hass.config.currency||"",c=o.avg_energy!=null&&o.cycle_count?o.avg_energy*o.cycle_count:null,p=v=>v?Math.round(v/60)+"m":"-",_=(this._profileHealth||{})[t.name],f=(this._profileTrends||{})[t.name],u=_&&_.health_status!=="unknown"?(()=>{const v={healthy:["var(--success-color,#4caf50)","rgba(76,175,80,.12)"],fair:["var(--warning-color,#ff9800)","rgba(255,152,0,.12)"],poor:["var(--error-color,#f44336)","rgba(244,67,54,.12)"]},[T,C]=v[_.health_status]||v.fair,x=Math.round((_.health_score||0)*100),$=_.duration_cv!=null?` \xB7 ${this._t("stat.duration_cv",{pct:Math.round(_.duration_cv*100)},`duration CV ${Math.round(_.duration_cv*100)}%`)}`:"",L=_.confidence_mean!=null?` \xB7 ${this._t("stat.avg_confidence",{pct:Math.round(_.confidence_mean*100)},`avg confidence ${Math.round(_.confidence_mean*100)}%`)}`:"";return`
${_.health_status==="poor"?this._t("health.poor",{},"\u26A0 Poor match fit"):_.health_status==="fair"?this._t("health.fair",{},"Fair match fit"):this._t("health.good",{},"\u2713 Good match fit")}
${this._t("stat.score",{pct:x},`score ${x}%`)}${$}${L}
${this._profileAdvisory(t.name,"poor_health")?`${d(this._advisoryText(this._profileAdvisory(t.name,"poor_health")))} `:""}
`})():"",b=f&&(f.duration_trend!=="stable"||f.energy_trend&&f.energy_trend!=="stable")?(()=>{const v=[];f.duration_trend==="up"?v.push(this._t("msg.trend_duration_longer",{pct:`${f.duration_slope_pct>0?"+":""}${f.duration_slope_pct}`,avg:`${Math.round(f.duration_recent_mean_s/60)}m`},`Duration trending longer (${f.duration_slope_pct>0?"+":""}${f.duration_slope_pct}%/cycle) \u2014 recent avg ${Math.round(f.duration_recent_mean_s/60)}m`)):f.duration_trend==="down"&&v.push(this._t("msg.trend_duration_shorter",{pct:`${f.duration_slope_pct}`,avg:`${Math.round(f.duration_recent_mean_s/60)}m`},`Duration trending shorter (${f.duration_slope_pct}%/cycle) \u2014 recent avg ${Math.round(f.duration_recent_mean_s/60)}m`)),f.energy_trend==="up"?v.push(this._t("msg.trend_energy_up",{pct:`${f.energy_slope_pct>0?"+":""}${f.energy_slope_pct}`,avg:gt(f.energy_recent_mean_wh)},`Energy trending up (${f.energy_slope_pct>0?"+":""}${f.energy_slope_pct}%/cycle) \u2014 recent avg ${gt(f.energy_recent_mean_wh)}`)):f.energy_trend==="down"&&v.push(this._t("msg.trend_energy_down",{pct:`${f.energy_slope_pct}`},`Energy trending down (${f.energy_slope_pct}%/cycle)`));const T=f.duration_trend==="up"||f.energy_trend==="up",C=this._profileAdvisory(t.name,"duration_trend_up")||this._profileAdvisory(t.name,"energy_trend_up");return`
${this._t("msg.performance_trend",{n:f.cycle_count},`Performance trend (${f.cycle_count} cycles)`)}
${v.map(L=>`${L} `).join(" ")}
${C?`${d(this._advisoryText(C))} `:""}
`})():"";r=`
${this._t("lbl.duration",{},"Duration")}
${p(o.avg_duration)}${this._t("stat.avg",{},"avg")}
${this._t("stat.min",{v:p(o.min_duration)},`min ${p(o.min_duration)}`)} \xB7 ${this._t("stat.max",{v:p(o.max_duration)},`max ${p(o.max_duration)}`)}${n.duration_std_dev!=null?` \xB7 ${this._t("stat.consistency",{v:`${Math.round(n.duration_std_dev/60)}m`},`consistency \xB1${Math.round(n.duration_std_dev/60)}m`)}`:""}
${this._t("lbl.energy",{},"Energy")}
${gt(o.avg_energy)}${this._t("stat.avg",{},"avg")}
${this._t("stat.total",{v:gt(c)},`total ${gt(c)}`)}
${o.avg_cost!=null?`
${this._t("lbl.avg_cost",{},"Avg cost")}
${It(o.avg_cost,l)}${this._t("stat.avg",{},"avg")}
${this._t("stat.total",{v:It(o.total_cost,l)},"total {v}")}
`:""}
${this._t("lbl.activity",{},"Activity")}
${o.cycle_count||0}${this._t("lbl.cycles_lc",{},"cycles")}
${this._t("stat.last_run",{v:o.last_run?et(o.last_run):"-"},`last run ${o.last_run?et(o.last_run):"-"}`)}
${u}
${b}
${this._profileAdvisory(t.name,"shape_drift")?`
\u26A0 ${d(this._advisoryText(this._profileAdvisory(t.name,"shape_drift")))}
`:""}
${n.avg&&n.avg.length?`
`:`${this._t("msg.no_envelope",{},"No envelope yet - rebuild after labelling cycles.")}
`}`}else if(t.tab==="phases"){const o=t.catalog||[],n=(t.phases||[]).map((c,p)=>{const _=o.map(f=>`${d(f)} `).join("");return`
${this._t("lbl.name_placeholder",{},"(name)")} ${_}
\u2013
${this._t("lbl.timer_min",{},"min")}
\u2715
`}).join(""),l=this._busy.has("pp-phase-save");r=`${this._t("msg.phase_ranges_intro",{},"Phase ranges (minutes from cycle start) overlaid on the average curve. Edit values to preview live.")}
${t.env&&t.env.avg&&t.env.avg.length?`
`:`${this._t("msg.no_envelope_overlay",{},"No envelope available to overlay.")}
`}
${n||`
${this._t("msg.no_phases_assigned",{},"No phases assigned.")}
`}
${e?`
${this._t("btn.add_phase",{},"+ Add phase")}
${l?' '+this._t("status.saving",{},"Saving\u2026"):this._t("btn.save_phases",{},"Save phases")}
`:""}`}else if(t.tab==="cleanup"){const o=t.cleanup&&t.cleanup.cycles||[],n=t.cleanup&&t.cleanup.selected||new Set,{col:l,dir:c}=this._cleanupSort,p={date:C=>C.start_time?new Date(C.start_time).getTime():0,duration:C=>C.duration,energy:C=>C.energy_kwh,status:C=>C.status||""},f=_e(o,p[l]||p.date,c).map((C,x)=>{const $=o.indexOf(C),L=e?`
${this._t("btn.trim_split",{},"Trim / Split")}
`:"";return`
${et(C.start_time)}
${ht(C.duration)}
${C.energy_kwh!=null?gt(C.energy_kwh):"-"}
${d(C.status||"completed")}
${L}
`}).join(""),u=`
${mt(this._t("lbl.date",{},"Date"),"date",l==="date",c,"cleanupsort")}
${mt(this._t("lbl.duration",{},"Duration"),"duration",l==="duration",c,"cleanupsort","right")}
${mt(this._t("lbl.energy",{},"Energy"),"energy",l==="energy",c,"cleanupsort","right")}
${mt(this._t("lbl.status",{},"Status"),"status",l==="status",c,"cleanupsort")}
${e?" ":""}
`,b=this._busy.has("pp-cleanup-del"),v=this._busy.has("pp-cleanup-unlabel"),T=b||v;r=`${this._t("msg.cleanup_intro",{},"All labelled cycles overlaid. Tick outliers, then unlabel them (kept in history, no longer shaping this profile) or delete them.")}
${o.length?`
`:`${this._t("msg.no_cycles_profile",{},"No cycles for this profile.")}
`}
${o.length?``:""}
${e?`
${v?' '+this._t("status.unlabelling",{},"Unlabelling\u2026"):this._t("btn.unlabel_selected",{n:n.size},`Unlabel selected (${n.size})`)}
${b?' '+this._t("status.deleting",{},"Deleting\u2026"):this._t("btn.delete_selected",{n:n.size},`Delete selected (${n.size})`)}
`:""}`}else if(t.tab==="danger"){const o=this._busy.has("pp-rebuild"),n=t.stats&&t.stats.avg_duration?Math.round(t.stats.avg_duration/60):0,l=!!(t.stats&&t.stats.duration_learned),c=l?this._t("msg.duration_learned_hint",{},"Worked out from this program's cycles (outliers left out) and updated after each new one. To change it, trim or remove the cycles that are off."):this._t("msg.manual_duration_hint",{},"The profile's expected cycle length, used for time-remaining estimates. Leave it unchanged to keep the current value.");r=`${this._t("lbl.rename_profile",{},"Rename Profile")}
${this._t("btn.save",{},"Save")}
${o?' '+this._t("status.rebuilding",{},"Rebuilding\u2026"):this._t("btn.rebuild_envelope",{},"Rebuild Envelope")}
${this._t("btn.delete_profile",{},"Delete Profile")}
`}const i=this._onlineEnabled()&&this._storeDeviceDeclared()?`\u2B06 ${this._t("btn.share_to_store",{},"Share to store")} `:"";return`${this._t("modal.profile_title",{name:t.name},"Profile \xB7 {name}")}
${a}
${r}
${this._t("btn.close",{},"Close")}
${i}
`}_cycleProfileOverlays(t){const e=[];if(!t||t.mode!=="view"&&t.mode!=="review")return e;const s=t.curve||{},a=t.overlayPlan||null,r=!!(a&&a.suspected),i=a&&a.name||s.profile_name||"profile",o=r?this._tText("lbl.suspected_profile",{name:i},"{name} (suspected)"):`${this._tText("lbl.expected",{},"Expected")} (${i})`,n=t.profileEnv,l=p=>p.target_duration||p.avg[p.avg.length-1][0]||0;!r&&(s.expected||[]).length>1?e.push({kind:"expected",points:s.expected,stroke:"#ff9800",width:2,alpha:.45,name:o}):n&&(n.avg||[]).length&&e.push({kind:r?"suspected":"expected",points:n.avg,stroke:"#ff9800",width:2,alpha:.45,dash:r,name:o,xEnd:l(n)});const c=r&&a.runnerUp?t.runnerUpEnv:null;return c&&(c.avg||[]).length&&e.push({kind:"runner_up",points:c.avg,stroke:"#9c27b0",width:1.6,alpha:.55,dash:!0,xEnd:l(c),name:this._tText("lbl.runner_up_profile",{name:a.runnerUp},"{name} (runner-up)")}),e}_drawCycleEditor(){const t=this._modal;if(!t||t.type!=="cycle-detail"||!t.loaded)return;const e=t.curve||{},s=e.samples||[];if(!s.length)return;let a=e.full_duration_s||s[s.length-1][0]||1;const r=[];if(this._cycleProfileOverlays(t).forEach(c=>{r.push(c),a=Math.max(a,c.xEnd||0)}),t.mode==="review"&&(t.overlays||[]).length){const c=this._profileEnvCache||{};(t.overlays||[]).forEach(p=>{const _=c[p];if(!_||!(_.avg||[]).length)return;const f=Q[Math.max(0,(this._profiles||[]).findIndex(b=>b.name===p))%Q.length];r.push({points:_.avg,stroke:f,width:1.6,alpha:.7,name:p});const u=_.avg[_.avg.length-1];a=Math.max(a,_.target_duration||(u?u[0]:0))})}const i=this._cycleContextSeries(t);i&&(r.forEach(c=>{c.bounded=!0}),r.push(i.series),a=Math.max(a,i.xEnd)),r.push({points:s,stroke:"primary",fill:!0,width:2,name:this._t("lbl.power",{},"Power"),bounded:!!i});const o=[],n=[];if(i){const c="rgba(127,127,127,.10)";i.xStarti.traceEnd&&o.push({x0:i.traceEnd,x1:i.xEnd,fill:c})}let l=[];if(t.mode==="trim"){const c=t.trim.start,p=t.trim.end;o.push({x0:0,x1:c,fill:"rgba(244,67,54,.18)"}),o.push({x0:p,x1:a,fill:"rgba(244,67,54,.18)"}),n.push({x:c,color:"#f44336",label:"S"},{x:p,color:"#f44336",label:"E"})}else if(t.mode==="split")(t.split.offsets||[]).slice().sort((c,p)=>c-p).forEach((c,p)=>n.push({x:c,color:"#ff9800",label:"#"+(p+1)}));else{l=e.artifacts||[];const c={pause:"rgba(255,152,0,.22)",dip:"rgba(33,150,243,.18)",spike:"rgba(244,67,54,.18)"};l.forEach(_=>o.push({x0:_.start_s,x1:Math.max(_.end_s,_.start_s+1),fill:c[_.type]||"rgba(158,158,158,.18)"}));const p=e.start_time;(e.restart_gaps||[]).forEach(_=>{if(!p)return;const f=new Date(p).getTime(),u=Math.max(0,(new Date(_.start_ts).getTime()-f)/1e3),b=Math.max(u+1,(new Date(_.end_ts).getTime()-f)/1e3);o.push({x0:u,x1:b,fill:"rgba(96,125,139,.20)",label:"\u21BB"})})}this._drawCurves("wd-cyc-canvas",{series:r,xMax:a,bands:o,vlines:n,artifacts:l,...i?{xStart:i.xStart,cycleSpan:[i.traceStart,i.traceEnd]}:{}})}_cycleContextMinutes(t){const e=this._pref("cycle_context_min",null),s=e&&typeof e=="object"?e[t]:void 0;return Ft.includes(s)?s:Ee}async _loadCycleContext(t,e,s){const a=this._modal;if(!a||a.type!=="cycle-detail"||a.cycleId!==e)return;const r=this._cycleContextMinutes(t);if(!r||!s||s.is_reference||!(s.samples||[]).length){a.ctx={minutes:r,loading:!1,data:null};return}a.ctx={minutes:r,loading:!0,data:null};let i=null;try{i=await this._ws({type:`${w}/get_cycle_context`,entry_id:t,cycle_id:e,before_s:r*60,after_s:r*60})}catch{i={available:!1,reason:"no_history"}}const o=this._modal;!o||o.type!=="cycle-detail"||o.cycleId!==e||o.entryId!==t||!o.ctx||o.ctx.minutes!==r||(o.ctx={minutes:r,loading:!1,data:i},this._render())}_cycleContextSeries(t){if(!t||t.mode!=="view"&&t.mode!=="review")return null;const e=t.ctx&&t.ctx.data,s=t.curve&&t.curve.samples||[];if(!e||!e.available||!s.length)return null;const a=s[0],r=s[s.length-1],i=[];let o=null;const n=(u,b)=>{if(o!=null&&i.push([u,o]),b==null){i.push([u,null]),o=null;return}i.push([u,b]),o=b},l=(u,b)=>(Array.isArray(u)?u:[]).filter(v=>Array.isArray(v)&&Number.isFinite(v[0])&&b(v[0])),c=l(e.before,u=>uu>=r[0]);if(c.forEach(([u,b])=>n(u,b)),o!=null&&(i.push([a[0],o]),i.push([a[0],a[1]])),p.length){i.length&&i.push([r[0],null]),o=null,n(r[0],r[1]),p.forEach(([b,v])=>n(b,v));const u=Number(e.after_end_s)||0;o!=null&&u>i[i.length-1][0]&&i.push([u,o])}if(!i.some(u=>u[1]!=null))return null;const _=c.length?Math.min(c[0][0],-(Number(e.before_s)||0)):a[0],f=p.length?Math.max(i[i.length-1][0],Number(e.after_end_s)||0):r[0];return{series:{kind:"context",points:i,stroke:"#9e9e9e",width:2,alpha:1,bounded:!0,aside:!0,name:this._tText("lbl.cycle_context_series",{},"Recorder history")},xStart:Math.min(0,_),xEnd:f,traceStart:a[0],traceEnd:r[0]}}_htmlCompareModal(t){const e=t.ids||[],s={};(this._cycles||[]).forEach(o=>{s[o.id]=o});const a=t.hidden||new Set,r=e.map((o,n)=>{const l=s[o]||{},c=Q[n%Q.length],p=!a.has(o),_=!!(t.cycles&&t.cycles[o]),f=`${et(l.start_time)||String(o).slice(0,8)} \xB7 ${Math.round((l.duration||0)/60)}m \xB7 ${d(l.profile_name||this._t("lbl.unlabelled",{},"Unlabelled"))}`;return` ${f}${_?"":` ${this._t("lbl.loading_paren",{},"(loading\u2026)")} `} `}).join(""),i=(this._profiles||[]).map(o=>{const n=(t.overlays||[]).includes(o.name),l=Q[Math.max(0,(this._profiles||[]).findIndex(p=>p.name===o.name))%Q.length],c=n?` `:"";return` ${c}${d(o.name)} `}).join("")||`${this._t("msg.no_profiles_overlay",{},"No profiles to overlay.")} `;return`${this._t("msg.compare_cycles_title",{count:e.length},`Compare ${e.length} cycles`)}
${t.loaded?"":`${this._t("msg.loading",{},"Loading\u2026")}
`}
${this._t("msg.compare_selected_cycles",{},"Selected cycles (solid) \u2013 show / hide")}
${r}
${this._t("msg.compare_overlay_profiles",{},"Overlay profiles (faint)")}${ot(this._t("msg.compare_overlay_tip",{},"Overlay learned profile envelopes to see which program each cycle resembles."))}
${i}
${this._t("btn.close",{},"Close")}
`}_drawCompareCanvas(){const t=this._modal;if(!t||t.type!=="compare-cycles")return;const e=t.ids||[],s={};(this._cycles||[]).forEach(n=>{s[n.id]=n});const a=t.hidden||new Set,r=[];let i=0;const o=this._profileEnvCache||{};(t.overlays||[]).forEach(n=>{const l=o[n];if(!l||!(l.avg||[]).length)return;const c=Q[Math.max(0,(this._profiles||[]).findIndex(_=>_.name===n))%Q.length];r.push({points:l.avg,stroke:c,width:2,alpha:.4,name:n});const p=l.avg[l.avg.length-1];i=Math.max(i,l.target_duration||(p?p[0]:0))}),e.forEach((n,l)=>{if(a.has(n))return;const c=t.cycles&&t.cycles[n],p=c&&c.samples;if(!p||!p.length)return;const _=Q[l%Q.length],f=s[n]||{};r.push({points:p,stroke:_,width:1.8,alpha:.9,name:et(f.start_time)||String(n).slice(0,8)}),i=Math.max(i,c.full_duration_s||p[p.length-1][0]||0)}),this._drawCurves("wd-compare-canvas",{series:r,xMax:i||1})}_drawProfileEnvelope(){const t=this._modal;if(!t||!t.env||!(t.env.avg||[]).length)return;const e=t.env;this._drawCurves("wd-env-canvas",{series:[{points:e.avg,stroke:"primary",width:2,name:"Average"}],band:{min:e.min,max:e.max},xMax:e.target_duration||e.avg[e.avg.length-1][0]})}_drawPhaseEditor(){const t=this._modal;if(!t||!t.env||!(t.env.avg||[]).length)return;const e=t.env,s=e.target_duration||e.avg[e.avg.length-1][0],a=(t.phases||[]).map((i,o)=>({x0:i.start,x1:i.end,fill:Pt(Q[o%Q.length],.2)})),r=[];(t.phases||[]).forEach((i,o)=>{const n=Q[o%Q.length];r.push({x:i.start,color:n,label:i.name?i.name.slice(0,7):"",handle:!0}),r.push({x:i.end,color:n,handle:!0})}),this._drawCurves("wd-phase-canvas",{series:[{points:e.avg,stroke:"primary",width:2,name:"Average"}],band:{min:e.min,max:e.max},bands:a,vlines:r,xMax:s})}_drawSpaghetti(){const t=this._modal;if(!t||!t.cleanup||!(t.cleanup.cycles||[]).length)return;const e=t.cleanup.cycles,s=t.cleanup.selected||new Set,a=this._spagTableHoverCid||null;let r=1;e.forEach(o=>{const n=o.samples||[];n.length&&n[n.length-1][0]>r&&(r=n[n.length-1][0])});const i=e.map((o,n)=>{const l=s.has(o.cycle_id),c=a===o.cycle_id;return{points:o.samples||[],stroke:Q[n%Q.length],width:l||c?2.6:1,alpha:s.size?l?1:.22:a?c?1:.22:.7,name:et(o.start_time),cid:o.cycle_id}});this._drawCurves("wd-spag-canvas",{series:i,xMax:r})}_wire(){const t=this.shadowRoot;if(!t)return;const e=t.getElementById("wd-burger");e&&e.addEventListener("click",()=>{this.dispatchEvent(new CustomEvent("hass-toggle-menu",{bubbles:!0,composed:!0}))}),t.querySelectorAll(".wd-devcard[data-idx]").forEach(m=>m.addEventListener("click",()=>this._guardUnsaved(()=>this._selectDevice(parseInt(m.dataset.idx,10))))),t.querySelectorAll("[data-tab]").forEach(m=>m.addEventListener("click",()=>{const g=m.dataset.tab,k=()=>{g!=="settings"&&(this._pendingSettings={},this._dirtyOptKeys=new Set),this._tab=g,this._fetchTabData()};if(this._tab==="settings"&&g!=="settings"){this._guardUnsaved(k);return}k()})),t.querySelectorAll("[data-sec]").forEach(m=>m.addEventListener("click",()=>{this._snapshotFormToPending(t),this._settingsSec=m.dataset.sec,this._settingsSearch="",this._settingsSugOnly=!1,this._render()})),t.querySelectorAll("[data-ptab]").forEach(m=>m.addEventListener("click",()=>{const g=this._panelSubtab=m.dataset.ptab;this._render();const k=this._devices[this._selIdx];k&&(g==="diagnostics"&&!this._diag?this._fetchToolsData(k.entry_id).then(()=>{this._panelSubtab==="diagnostics"&&this._render()}):g==="logs"?this._fetchLogs().then(()=>{this._panelSubtab==="logs"&&this._render()}):g==="ml"&&this._fetchTabData())})),t.querySelectorAll("[data-hsub]").forEach(m=>m.addEventListener("click",()=>{this._historySub=m.dataset.hsub,this._render();const g=this._devices[this._selIdx];this._historySub==="maintenance"&&g?this._fetchMaintenance(g.entry_id).then(()=>{this._tab==="history"&&this._render()}):this._fetchTabData()})),t.querySelectorAll("[data-gtab]").forEach(m=>m.addEventListener("click",()=>{this._gearTab=m.dataset.gtab,this._modal&&this._modal.type==="gear-settings"&&(this._modal.tab=this._gearTab);const g=this._devices[this._selIdx];this._gearTab==="online"&&g&&this._onlineEnabled()&&(this._ensureStoreConnectListener(),this._loadStoreStatus(g.entry_id).then(()=>{this._modal&&this._modal.type==="gear-settings"&&this._render()})),this._render()}));const s=(m,g)=>{this._pendingSettings[m]=g,this._dirtyOptKeys.add(m)},a=t.getElementById("wd-store-brand");a&&a.addEventListener("change",()=>{const m=a.value.trim();if(m!==String(this._editedOpts().store_brand||"").trim()){s("store_model","");const g=t.getElementById("wd-store-model");g&&(g.value="")}s("store_brand",m),this._catalog.forBrand=m,this._catalog.devices=void 0,this._renderPreservingFormEdits()});const r=t.getElementById("wd-store-model");r&&r.addEventListener("change",()=>{s("store_model",r.value.trim()),this._renderPreservingFormEdits()});const i=t.getElementById("wd-pg-canvas");if(i){const m=()=>{const S=i.getBoundingClientRect(),F=S.height||330,N=Bt+8,H=F-N-52;return{rect:S,padT:N,powerH:H}},g=(S,F)=>{const N=i.getBoundingClientRect(),H=S-N.left,Z=F-N.top;if(Z>Bt+4)return null;const tt=this._pgEventHits||[];let st=null,rt=1/0;for(const lt of tt){const at=(H-lt.cx)**2+(Z-lt.cy)**2;at<=(lt.r+4)**2&&at{const{rect:F,padT:N,powerH:H}=m(),Z=this._pgPowerPts;if(!Z?.length)return 0;const tt=Math.max(...Z.map(st=>st.w),1);return Math.max(0,(1-Math.max(0,S-F.top-N)/H)*tt)},y=S=>{const{rect:F,padT:N,powerH:H}=m(),Z=this._pgPowerPts;if(!Z?.length)return 0;const tt=Math.max(...Z.map(st=>st.w),1);return F.top+N+(1-Math.max(0,+S)/tt)*H},P=S=>{const F=this._pgMap;if(!F)return null;const{rect:N}=m(),H=(S-N.left-F.padLpx)/Math.max(1,F.plotWpx);return F.vMin+Math.max(0,Math.min(1,H))*(F.vMax-F.vMin)};i.addEventListener("pointermove",S=>{if(q.has(S.pointerId)&&q.set(S.pointerId,{x:S.clientX,y:S.clientY}),i._wdPinching){E();return}if(this._pgDragging==="start_thr"){this._pgThreshStart=Math.max(0,k(S.clientY)),this._pgUpdateParamInput("start_threshold_w",this._pgThreshStart),this._pgDrawCanvas();return}if(this._pgDragging==="stop_thr"){this._pgThreshStop=Math.max(0,k(S.clientY)),this._pgUpdateParamInput("stop_threshold_w",this._pgThreshStop),this._pgDrawCanvas();return}if(this._pgDragging==="pan"&&this._pgPanStart){const st=this._pgMap,rt=(S.clientX-this._pgPanStart.clientX)/Math.max(1,st.plotWpx),lt=this._pgPanStart.vMax-this._pgPanStart.vMin;let at=this._pgPanStart.vMin-rt*lt;at=Math.max(0,Math.min(this._pgPanStart.totalDur-lt,at)),this._pgView={min:at,max:at+lt},this._pgDrawCanvas();return}if(!this._pgPowerPts?.length)return;const F=g(S.clientX,S.clientY);if(F){i.style.cursor="pointer",this._pgHoverEvent={t:F.t,type:F.type},this._pgHoverT=null,this._pgDrawCanvas();return}this._pgHoverEvent&&(this._pgHoverEvent=null,this._pgDrawCanvas());const N=this._pgThreshStart??this._pgFieldVal("start_threshold_w",{})??50,H=this._pgThreshStop??this._pgFieldVal("stop_threshold_w",{})??5,Z=Math.abs(S.clientY-y(N))<8||Math.abs(S.clientY-y(H))<8;i.style.cursor=Z?"ns-resize":"crosshair";const tt=P(S.clientX);tt!=null&&(this._pgHoverT=tt,this._pgUpdateStripAt(tt),this._pgDrawCanvas())});const X=(S,F)=>{const N=this._pgMap;if(!N||S==null)return;const H=N.vMax-N.vMin,Z=Math.max(30,Math.min(N.totalDur,H*F)),tt=H>0?Math.max(0,Math.min(1,(S-N.vMin)/H)):.5;let st=S-tt*Z;st=Math.max(0,Math.min(N.totalDur-Z,st)),this._pgView=Z>=N.totalDur-1?null:{min:st,max:st+Z},this._pgDrawCanvas()},q=new Map;let j=null;i.addEventListener("pointerdown",S=>{if(!this._pgPowerPts?.length)return;if(q.set(S.pointerId,{x:S.clientX,y:S.clientY}),q.size===2&&this._pgMap){i._wdPinching=!0,i._wdOwnGesture=!1,this._pgDragging=null,this._pgPanStart=null,i.classList.remove("wd-pg-panning");const[Z,tt]=[...q.values()];j={dist:Math.max(1,Math.abs(Z.x-tt.x)),centerX:(Z.x+tt.x)/2};return}if(q.size!==1||g(S.clientX,S.clientY))return;i.setPointerCapture(S.pointerId);const F=this._pgThreshStart??this._pgFieldVal("start_threshold_w",{})??50,N=this._pgThreshStop??this._pgFieldVal("stop_threshold_w",{})??5,H=S.pointerType==="touch"?18:10;Math.abs(S.clientY-y(F)){try{i.releasePointerCapture(S.pointerId)}catch{}q.delete(S.pointerId),q.size<2&&(j=null,i._wdPinching=!1),q.size===0&&(i._wdOwnGesture=!1);const F=this._pgDragging==="start_thr"||this._pgDragging==="stop_thr";return this._pgDragging=null,this._pgPanStart=null,i.classList.remove("wd-pg-panning"),F};i.addEventListener("pointerup",S=>{V(S)&&this._pgDrawCanvas()}),i.addEventListener("pointercancel",S=>{V(S)&&this._pgDrawCanvas(),this._pgHoverT=null,this._pgHoverEvent=null,this._pgUpdateStripAt(null),this._pgDrawCanvas()}),i.addEventListener("pointerleave",S=>{this._pgDragging||S.pointerType==="touch"||(this._pgHoverT=null,this._pgHoverEvent=null,this._pgUpdateStripAt(null),this._pgDrawCanvas())}),i.addEventListener("wheel",S=>{if(!this._pgPowerPts?.length||!this._pgMap)return;S.preventDefault();const F=S.ctrlKey?Math.min(4,Math.max(.25,Math.exp(S.deltaY*.01))):S.deltaY>0?1.25:.8;X(P(S.clientX),F)},{passive:!1}),i.addEventListener("dblclick",S=>{if(this._pgView){this._pgView=null,this._pgDrawCanvas();return}X(P(S.clientX),Qt)});const E=()=>{const S=this._pgMap;if(!S||!j||q.size<2)return;const[F,N]=[...q.values()],H=Math.max(1,Math.abs(F.x-N.x)),Z=(F.x+N.x)/2,tt=S.vMax-S.vMin,st=Math.max(30,Math.min(S.totalDur,tt*(j.dist/H))),rt=P(Z),lt=(Z-j.centerX)/Math.max(1,S.plotWpx)*st;if(j.dist=H,j.centerX=Z,rt==null)return;const at=tt>0?Math.max(0,Math.min(1,(rt-S.vMin)/tt)):.5;let pt=rt-at*st-lt;pt=Math.max(0,Math.min(S.totalDur-st,pt)),this._pgView=st>=S.totalDur-1?null:{min:pt,max:pt+st},this._pgDrawCanvas()};this._attachTouchOwnershipGuard(i)}t.querySelectorAll("input[data-pgkey]").forEach(m=>{m.addEventListener("input",()=>{const g=m.dataset.pgkey;if(m.dataset.pgtype==="bool"){const q=m.checked;g==="start_threshold_w"?this._pgThreshStart=q:g==="stop_threshold_w"?this._pgThreshStop=q:this._pgParamOverrides[g]=q,this._render();const j=t.querySelector(`input[data-pgkey="${g}"]`);j&&j.focus({preventScroll:!0}),requestAnimationFrame(()=>this._pgDrawCanvas());return}const k=m.value.trim();if(!k)g==="start_threshold_w"?this._pgThreshStart=null:g==="stop_threshold_w"?this._pgThreshStop=null:delete this._pgParamOverrides[g];else{const q=parseFloat(k);isNaN(q)||(g==="start_threshold_w"?this._pgThreshStart=q:g==="stop_threshold_w"?this._pgThreshStop=q:this._pgParamOverrides[g]=q)}const y=m.selectionStart,P=m.value;this._render();const X=t.querySelector(`input[data-pgkey="${g}"]`);if(X){X.value=P,X.focus({preventScroll:!0});try{X.setSelectionRange(y,y)}catch{}}requestAnimationFrame(()=>this._pgDrawCanvas())})});const o=t.getElementById("wd-pg-cyc-sel");o&&o.addEventListener("change",()=>this._pgSelectCycle(o.value));const n=t.getElementById("wd-pg-prof-sel");n&&n.addEventListener("change",()=>{this._pgProfileName=n.value,this._pgLoad()});const l=t.getElementById("wd-pg-preset-sel");l&&l.addEventListener("change",()=>{this._pgPresetSel=l.value,this._render()});const c=t.getElementById("wd-pg-preset-name");c&&c.addEventListener("input",()=>{this._pgPresetName=c.value;const m=(this._pgPresetName||"").trim(),g=this._pgPresetAtLimit(m),k=t.querySelector('[data-action="pg-preset-save"]');k&&(k.disabled=!(m&&!g));const y=t.getElementById("wd-pg-preset-limit-note");y&&(y.style.display=g?"":"none")}),t.querySelectorAll("#wd-pg-simn, #wd-pg-sw-n").forEach(m=>m.addEventListener("input",()=>{this._pgSimCycles=Math.max(1,Math.min(St,parseInt(m.value,10)||20))}));const p=t.getElementById("wd-pg-sw-param");p&&p.addEventListener("change",()=>{this._pgSweepParam=p.value,this._pgSweepNew=null,this._render()});const _=t.getElementById("wd-pg-sw-obj");_&&_.addEventListener("change",()=>{this._pgSweepObjective=_.value,this._pgSweepNew=null,this._render()});const f=t.getElementById("wd-pg-sw-paramy"),u=t.getElementById("wd-pg-sw-from");u&&u.addEventListener("input",()=>{this._pgSweepFrom=u.value});const b=t.getElementById("wd-pg-sw-to");b&&b.addEventListener("input",()=>{this._pgSweepTo=b.value});const v=t.getElementById("wd-pg-sw-steps");v&&v.addEventListener("input",()=>{this._pgSweepSteps=parseInt(v.value,10)||5}),t.querySelectorAll("[data-statustoggle]").forEach(m=>m.addEventListener("change",async()=>{const g=m.dataset.statustoggle,k=m.checked;this._panelCfg||(this._panelCfg={}),this._panelCfg.prefs={...this._panelCfg.prefs||{},[g]:k},this._ws({type:`${w}/set_user_prefs`,prefs:{[g]:k}}).catch(()=>{});const y=this._devices[this._selIdx];if(y&&this._tab==="status")try{this._powerData=await this._ws({type:`${w}/get_power_history`,entry_id:y.entry_id,with_raw:this._pref("show_raw_active",!1)})}catch{}this._render()})),t.querySelectorAll("[data-sortact]").forEach(m=>m.addEventListener("click",()=>{const g=m.dataset.sortact,k=m.dataset.sortcol,y=P=>{P.col===k?P.dir*=-1:(P.col=k,P.dir=k==="date"?-1:1)};g==="cycsort"?y(this._cycleSort):g==="cleanupsort"&&y(this._cleanupSort),this._render()}));const T=t.getElementById("wd-cyc-filter-text");T&&T.addEventListener("input",m=>{const g=m.target.selectionStart;this._cycleFilter.text=T.value,this._render();const k=this.shadowRoot.getElementById("wd-cyc-filter-text");k&&(k.focus({preventScroll:!0}),k.setSelectionRange(g,g))});const C=t.getElementById("wd-settings-search");C&&C.addEventListener("input",m=>{const g=m.target.selectionStart;this._settingsSearch=C.value,C.value.trim()&&(this._settingsSugOnly=!1),this._render();const k=this.shadowRoot.getElementById("wd-settings-search");k&&(k.focus({preventScroll:!0}),k.setSelectionRange(g,g))});const x=t.getElementById("wd-cyc-filter-status");x&&x.addEventListener("change",()=>{this._cycleFilter.status=x.value,this._render()});const $=m=>{const g=document.createElement("span");g.className="wd-pill",g.dataset.val=m,g.title=m;const k=document.createElement("span");k.className="wd-pill-txt",k.textContent=m,g.appendChild(k);const y=document.createElement("button");return y.type="button",y.className="wd-pill-x",y.setAttribute("aria-label",this._tText("btn.remove",{},"Remove")),y.textContent="\xD7",y.addEventListener("click",()=>g.remove()),g.appendChild(y),g};t.querySelectorAll(".wd-pillbox").forEach(m=>{const g=m.querySelector(".wd-pill-add"),k=y=>{const P=String(y||"").trim();if(!P)return;Array.from(m.querySelectorAll(".wd-pill")).some(q=>q.dataset.val===P)||m.insertBefore($(P),g.closest(".wd-combo")||g),g&&(g.value="")};m.querySelectorAll(".wd-pill-x").forEach(y=>y.addEventListener("click",()=>y.closest(".wd-pill")?.remove())),g&&(g.addEventListener("change",()=>k(g.value)),g.addEventListener("keydown",y=>{y.key==="Enter"&&(y.preventDefault(),k(g.value))}),g.addEventListener("blur",()=>k(g.value)))}),t.querySelectorAll(".wd-combo").forEach(m=>{const g=m.querySelector(".wd-combo-inp, .wd-pill-add"),k=m.querySelector(".wd-combo-drop");if(!g||!k)return;const y=m.classList.contains("wd-combo-pill"),P=g.dataset.catalog||g.dataset.opt||m.closest("[data-opt]")?.dataset.opt,X=(j,V=!1)=>{this._ensureCatalogList(P,j,V);const E=(this._entityListCache||{})[P]||[],S=(j||"").toLowerCase(),F=S?E.filter(N=>String(N).toLowerCase().includes(S)).slice(0,40):E.slice(0,20);if(!F.length){k.hidden=!0;return}k.innerHTML=F.map(N=>`${d(N)}
`).join(""),k._kbd=-1,k.hidden=!1,this._positionComboDrop(g,k)},q=j=>{if(j){if(y){const V=m.closest(".wd-pillbox");V&&!Array.from(V.querySelectorAll(".wd-pill")).some(E=>E.dataset.val===j)&&V.insertBefore($(j),m),g.value=""}else g.value=j,g.dispatchEvent(new Event("change",{bubbles:!0}));k.hidden=!0}};g.addEventListener("focus",()=>X(g.value)),g.addEventListener("input",()=>X(g.value,!g._wdSyntheticInput)),g.addEventListener("blur",()=>setTimeout(()=>{k.hidden=!0},150)),g.addEventListener("keydown",j=>{if(k.hidden&&j.key!=="ArrowDown")return;const V=k.querySelectorAll(".wd-combo-item");let E=k._kbd||-1;if(j.key==="ArrowDown"){if(j.preventDefault(),k.hidden){X(g.value);return}E=Math.min(E+1,V.length-1)}else if(j.key==="ArrowUp")j.preventDefault(),E=Math.max(E-1,0);else if(j.key==="Enter"&&!k.hidden){j.preventDefault(),E>=0?q(V[E].dataset.val):y&&g.value.trim()&&q(g.value.trim());return}else if(j.key==="Escape"){k.hidden=!0;return}else return;k._kbd=E,V.forEach((S,F)=>S.classList.toggle("kbd",F===E)),V[E]?.scrollIntoView({block:"nearest"})}),k.addEventListener("mousedown",j=>{const V=j.target.closest(".wd-combo-item");V&&(j.preventDefault(),q(V.dataset.val))})});const L=t.getElementById("wd-store-q");if(L){const m=()=>{const g=String(L.value||"").trim(),k=(this._catalog.modelIndex||new Map).get(g);if(k){this._storeOpenModel(k);return}g.toLowerCase()!==String(this._storeQuery||"").trim().toLowerCase()&&this._storeSearch(g)};L.addEventListener("change",m),L.addEventListener("keydown",g=>{if(g.key!=="Enter")return;const k=L.parentElement&&L.parentElement.querySelector(".wd-combo-drop");k&&!k.hidden&&k._kbd>=0||(g.preventDefault(),m())}),L.addEventListener("input",()=>{L._wdSyntheticInput||(clearTimeout(this._storeQTimer),this._storeQTimer=setTimeout(()=>{const g=String(L.value||"").trim();if(!g)return;((this._entityListCache||{}).store_brand||[]).some(y=>String(y).toLowerCase()===g.toLowerCase())&&m()},450))})}t.querySelectorAll(".wd-timerlist").forEach(m=>{const g=m.dataset.opt,k=()=>{if(!Array.isArray(this._pendingSettings[g])){const V=Array.isArray(this._opts&&this._opts[g])?this._opts[g]:[];this._pendingSettings[g]=V.map(E=>({...E}))}return this._pendingSettings[g]},y=V=>({offset_minutes:parseFloat(V.querySelector('[data-field="offset_minutes"]').value)||0,message:(V.querySelector('[data-field="message"]').value||"").trim(),auto_pause:V.querySelector('[data-field="auto_pause"]').checked}),P=V=>{const E=parseInt(V.dataset.tidx,10);if(isNaN(E))return;const S=k();S[E]=y(V)},X=V=>{const E=parseInt(V.dataset.tidx,10);isNaN(E)||(this._pendingSettings[g]=k().filter((S,F)=>F!==E)),V.remove(),m.querySelectorAll(".wd-timer-row").forEach((S,F)=>{S.dataset.tidx=F})},q=V=>{const E=V.querySelector(".wd-timer-remove");E&&E.addEventListener("click",()=>X(V)),V.querySelector('[data-field="auto_pause"]')?.addEventListener("change",()=>P(V)),V.querySelectorAll('[data-field="offset_minutes"],[data-field="message"]').forEach(S=>S.addEventListener("input",()=>P(V)))};m.querySelectorAll(".wd-timer-row").forEach(V=>q(V));const j=m.querySelector(".wd-timer-add");j&&j.addEventListener("click",()=>{const V=m.querySelectorAll(".wd-timer-row").length;k().push({offset_minutes:0,message:"",auto_pause:!1});const E=document.createElement("div");E.className="wd-timer-row",E.dataset.tidx=V,E.innerHTML=`
`,q(E),m.insertBefore(E,j)})});const D=t.getElementById("wd-status-prog");D&&D.addEventListener("change",()=>{const m=this._devices[this._selIdx];if(!m)return;const g=D.value,k=!Mt.includes(m.detector_state||"");this._ws({type:`${w}/set_program`,entry_id:m.entry_id,program:g}).then(()=>(this._showToast(g==="auto_detect"?this._t("msg.toast_auto_detect_enabled",{},"Auto-detect enabled"):k?this._tText("msg.toast_program_armed",{program:g},`Program armed for the next cycle: ${g}`):this._tText("msg.toast_program_set",{program:g},`Program set: ${g}`)),this._fetchAll())).catch(y=>this._showToast(this._tText("msg.toast_failed",{error:y.message||y},"Failed: "+(y.message||y)),"error"))}),t.querySelectorAll("[data-cid]").forEach(m=>m.addEventListener("click",g=>{if(g.target.tagName==="INPUT"||m.dataset.action)return;const k=m.dataset.cid;m.dataset.selmode==="1"?(this._cycleSel.has(k)?this._cycleSel.delete(k):this._cycleSel.add(k),this._render()):this._onAction({dataset:{action:"open-cycle",cid:k}})})),t.querySelectorAll('tr[data-cid][role="button"]').forEach(m=>m.addEventListener("keydown",g=>{g.target!==m||g.key!=="Enter"&&g.key!==" "||(g.preventDefault(),m.click())})),t.querySelectorAll(".wd-cyc-overlay").forEach(m=>m.addEventListener("change",()=>{const g=this._modal;if(!g||g.type!=="cycle-detail")return;this._snapshotCycleReviewForm(t);const k=new Set(g.overlays||[]);m.checked?k.add(m.value):k.delete(m.value),g.overlays=[...k];const y=this._devices[this._selIdx];m.checked&&y?this._ensureProfileEnvs(y.entry_id,[m.value]).then(()=>this._render()):this._render()})),t.querySelectorAll(".wd-compare-cyc").forEach(m=>m.addEventListener("change",()=>{const g=this._modal;if(!g||g.type!=="compare-cycles")return;const k=g.hidden instanceof Set?g.hidden:new Set(g.hidden||[]);m.checked?k.delete(m.value):k.add(m.value),g.hidden=k,this._render()})),t.querySelectorAll(".wd-compare-overlay").forEach(m=>m.addEventListener("change",()=>{const g=this._modal;if(!g||g.type!=="compare-cycles")return;const k=new Set(g.overlays||[]);m.checked?k.add(m.value):k.delete(m.value),g.overlays=[...k];const y=this._devices[this._selIdx];m.checked&&y?this._ensureProfileEnvs(y.entry_id,[m.value]).then(()=>this._render()):this._render()})),t.querySelectorAll(".wd-pg-mem").forEach(m=>m.addEventListener("change",()=>{const g=this._modal;if(!g||g.type!=="profile-group")return;const k=t.getElementById("wd-pg-name");k&&(g.name=k.value);const y=new Set(g.members||[]);m.checked?y.add(m.value):y.delete(m.value),g.members=[...y],this._render()}));const B=t.getElementById("wd-pg-name");B&&B.addEventListener("input",()=>{const m=this._modal;m&&m.type==="profile-group"&&(m.name=B.value)}),t.querySelectorAll(".wd-csel").forEach(m=>m.addEventListener("change",()=>{const g=m.closest("[data-cid]"),k=g&&g.dataset.cid;k&&(m.checked?this._cycleSel.add(k):this._cycleSel.delete(k),this._render())}));const U=t.getElementById("wd-merge-prof");U&&U.addEventListener("change",()=>{const m=t.getElementById("wd-merge-new");m&&(m.style.display=U.value==="__create_new__"?"":"none")}),t.querySelectorAll(".wd-log-filter").forEach(m=>{const g=m.dataset.logfilter,k={level:"_logLevel",device:"_logDevice",component:"_logComponent",search:"_logSearch"}[g];if(!k)return;const y=g==="search"?"input":"change";m.addEventListener(y,()=>{this[k]=m.value,this._refreshLogViews(),this._syncLogFilters(m)})});const z=t.querySelector(".wd-log-resize");z&&z.addEventListener("pointerdown",m=>{m.preventDefault(),z.setPointerCapture(m.pointerId),z.classList.add("dragging");const g=t.querySelector(".wd-log-drawer"),k=m.clientX,y=g.offsetWidth,P=X=>{const q=Math.max(280,Math.min(900,y+(k-X.clientX)));g.style.width=q+"px",this._logDrawerWidth=q};z.addEventListener("pointermove",P),z.addEventListener("pointerup",()=>{z.removeEventListener("pointermove",P),z.classList.remove("dragging");try{localStorage.setItem("wd-log-width",String(this._logDrawerWidth))}catch{}},{once:!0})});const M=t.getElementById("wd-hist-file");M&&M.addEventListener("change",()=>{const m=M.files&&M.files[0];if(!m)return;const g=this._modal,k=new FileReader;k.onload=()=>{const y=t.getElementById("wd-hist-csv"),P=String(k.result||"");y&&(y.value=P),g&&g.type==="history-import"&&(g.csvText=P)},k.onerror=()=>this._showToast(this._t("toast.file_read_failed",{},"Could not read that file"),"error"),k.readAsText(m)}),t.querySelectorAll("[data-hist-pick]").forEach(m=>m.addEventListener("change",()=>{const g=this._modal;if(!g||g.type!=="history-import")return;const k=parseInt(m.dataset.histPick,10);m.checked?g.accept.add(k):g.accept.delete(k),this._render(),requestAnimationFrame(()=>this._drawHistorySparklines())}));const A=t.getElementById("wd-import-file");A&&A.addEventListener("change",()=>{const m=A.files&&A.files[0];if(!m)return;const g=new FileReader;g.onload=()=>{const k=t.getElementById("wd-import-json");k&&(k.value=String(g.result||""))},g.readAsText(m)}),t.querySelectorAll("[data-stab]").forEach(m=>m.addEventListener("click",()=>{this._toolsSubtab=m.dataset.stab;const g=this._devices[this._selIdx];g&&(this._tabLoading=!0,this._render(),this._fetchToolsData(g.entry_id).then(()=>{this._tabLoading=!1,this._render()}))})),t.querySelectorAll("[data-proftab]").forEach(m=>m.addEventListener("click",async()=>{this._profSubtab=m.dataset.proftab;const g=this._devices[this._selIdx];g&&this._profSubtab==="phase-catalog"&&!this._phases.length&&(this._tabLoading=!0,this._render(),await this._fetchPhases(g.entry_id),this._tabLoading=!1),this._render()}));const O=t.getElementById("wd-settings-save");O&&O.addEventListener("click",()=>this._saveSettings());const K=t.getElementById("wd-ml-save");K&&K.addEventListener("click",()=>this._saveSettings());const I=t.getElementById("wd-settings-form"),R=m=>{const g=m.target&&m.target.closest?m.target.closest("[data-opt]"):null;g&&g.dataset.opt&&this._dirtyOptKeys.add(g.dataset.opt)},G=m=>{const g=m.target;if(!g||!g.closest)return;const k=g.closest(".wd-colorfield");if(!k)return;const y=k.querySelector("input[data-opt]"),P=k.querySelector(".wd-color-sw");!y||!P||(g===P?(y.value=String(P.value).toUpperCase(),this._dirtyOptKeys.add(y.dataset.opt)):g===y&&Kt.test(y.value.trim())&&(P.value=y.value.trim().toLowerCase()))};I&&(I.addEventListener("submit",m=>m.preventDefault()),I.addEventListener("input",m=>{G(m),R(m),this._liveValidateSettings(t)}),I.addEventListener("change",m=>{G(m),R(m),this._liveValidateSettings(t)}),I.addEventListener("click",m=>{const g=m.target.closest&&m.target.closest(".wd-color-clear");if(!g)return;const k=g.parentElement&&g.parentElement.querySelector("input[data-opt]");if(!k)return;k.value="";const y=g.parentElement.querySelector(".wd-color-sw");y&&(y.value=ye(k.placeholder)),this._dirtyOptKeys.add(k.dataset.opt),this._liveValidateSettings(t)}),I.addEventListener("click",m=>{const g=m.target.closest(".wd-conflict-fix");if(!g)return;const k=g.dataset.ckey,y=parseFloat(g.dataset.cval),P=I.querySelector(`[data-opt="${k}"]`);P&&!isNaN(y)&&(P.value=y,this._dirtyOptKeys.add(k),this._cascadeConflictFix(t,I,k))}),this._liveValidateSettings(t));const W=t.getElementById("wd-ml-form");W&&(W.addEventListener("input",R),W.addEventListener("change",R));const J=t.getElementById("wd-settings-revert");J&&J.addEventListener("click",async()=>{if(!this._prevOpts)return;const m=this._devices[this._selIdx];m&&await this._busyRun("save-settings",async()=>{try{const g=this._prevOpts;await this._ws({type:`${w}/set_options`,entry_id:m.entry_id,options:g}),this._opts={...g},this._prevOpts=null,this._cascadePending={},this._preCascadeOpts=null,this._pendingSettings={},this._dirtyOptKeys=new Set,this._showToast(this._t("toast.settings_reverted",{},"Settings reverted; integration reloading")),this._render()}catch(g){this._showToast(this._tText("msg.toast_revert_failed",{error:g.message||g},"Revert failed: "+(g.message||g)),"error")}})});const Y=t.getElementById("wd-settings-reload");Y&&Y.addEventListener("click",async()=>{const m=this._devices[this._selIdx];if(m){this._prevOpts=null,this._cascadePending={},this._preCascadeOpts=null,this._pendingSettings={},this._dirtyOptKeys=new Set;const g=await this._ws({type:`${w}/get_options`,entry_id:m.entry_id});this._opts=g.options||{},this._optDefaults=g.defaults||{},await this._fetchSuggestions(m.entry_id),this._render()}});const nt=t.getElementById("wd-pref-fontscale");if(nt){const m=t.getElementById("wd-pref-fontscale-val");nt.addEventListener("input",()=>{const g=parseFloat(nt.value)||1;this._applyFontScale(g),m&&(m.textContent=Math.round(g*100)+"%")}),nt.addEventListener("change",()=>{this._setPref("font_scale",parseFloat(nt.value)||1)})}t.querySelectorAll("[data-action]").forEach(m=>m.addEventListener("click",g=>this._onAction(g.currentTarget))),t.querySelectorAll("[data-maction]").forEach(m=>m.addEventListener("click",g=>this._onModalAction(g.currentTarget.dataset.maction,g.currentTarget))),t.querySelectorAll("select[data-maction]").forEach(m=>m.addEventListener("change",g=>this._onModalAction(g.currentTarget.dataset.maction,g.currentTarget))),t.querySelectorAll("input[data-indeterminate]").forEach(m=>{m.indeterminate=!0});const _t=t.querySelector("[data-toast-undo]");_t&&_t.addEventListener("click",()=>this._undoDelete(_t.dataset.toastUndo)),t.querySelectorAll("[data-suglock]").forEach(m=>m.addEventListener("click",async()=>{const g=m.dataset.suglock,k=this._devices[this._selIdx],y=k&&k.entry_id;if(!(!y||!g))try{if(await this._ws({type:`${w}/set_suggestion_lock`,entry_id:y,key:g,locked:!0}),!this._isActiveEntry(y))return;this._suggestions=(this._suggestions||[]).filter(P=>P.key!==g),(this._lockedSuggestions||[]).includes(g)||(this._lockedSuggestions=this._lockedSuggestions||[]).push(g),this._showToast(this._t("msg.sug_muted",{},"Won't suggest this setting again"),"info"),this._render()}catch{this._showToast(this._t("msg.sug_mute_failed",{},"Could not mute suggestion"),"error")}})),t.querySelectorAll("[data-sugkey]").forEach(m=>m.addEventListener("click",()=>{const g=m.dataset.sugkey,k=m.dataset.sugval,y=parseFloat(k);this._pendingSettings[g]=isNaN(y)?k:y,this._dirtyOptKeys.add(g),this._stagedSuggestions=!0,this._suggestions=this._suggestions.filter(q=>q.key!==g),this._showToast(this._tText("msg.sug_staged",{key:g,val:k},`Set ${g} = ${k}. Save to apply.`),"info"),this._render();const P=this.shadowRoot,X=P?.getElementById("wd-settings-form");X&&this._cascadeConflictFix(P,X,g)}));const ct=t.getElementById("wd-label-profile");ct&&ct.addEventListener("change",()=>{const m=t.getElementById("wd-new-profile-row"),g=ct.value==="__create_new__";if(m&&(m.style.display=g?"":"none"),!g){const k=t.getElementById("wd-new-profile-name");k&&(k.value="")}});const dt=t.getElementById("wd-cp-cycle");if(dt){const m=()=>{const g=t.getElementById("wd-cp-dur");if(!g)return;const k=!!dt.value;g.disabled=k,g.style.opacity=k?"0.5":""};dt.addEventListener("change",m),m()}const ut=t.getElementById("wd-pr-mode");ut&&ut.addEventListener("change",()=>{const m=t.getElementById("wd-pr-profile"),g=t.getElementById("wd-pr-existing");if(!m||!g)return;const k=ut.value==="existing_profile";m.style.display=k?"none":"",g.style.display=k?"":"none"}),this._wireCycleCanvas(t),this._wirePhaseInputs(t),this._wirePhaseCanvas(t),this._wireCleanup(t),this._wireSplitSegments(t)}_syncTrimInputs(){const t=this.shadowRoot,e=this._modal,s=(e.timeMode||"s")==="clock",a=t.getElementById("wd-trim-start"),r=t.getElementById("wd-trim-end");a&&(a.value=s?this._offsetToClock(e.trim.start):Math.round(e.trim.start)),r&&(r.value=s?this._offsetToClock(e.trim.end):Math.round(e.trim.end))}_snapTrimBounds(){const t=this._modal,e=t.curve&&t.curve.samples||[];if(e.length<2)return;const s=a=>e.reduce((r,i)=>Math.abs(i[0]-a)r[0]===t.trim.start);a>=0&&a+10&&(t.trim.start=e[a-1][0])}}_offsetToClock(t){const e=this._modal,s=e&&e.curve&&e.curve.start_time;if(!s)return"";const a=new Date(new Date(s).getTime()+(t||0)*1e3);return`${String(a.getHours()).padStart(2,"0")}:${String(a.getMinutes()).padStart(2,"0")}:${String(a.getSeconds()).padStart(2,"0")}`}_clockToOffset(t){const e=this._modal,s=e&&e.curve&&e.curve.start_time;if(!s||!t)return null;const a=String(t).split(":").map(Number);if(!a.length||a.some(l=>!Number.isFinite(l)))return null;const r=new Date(s),i=new Date(r);i.setHours(a[0]||0,a[1]||0,a[2]||0,0);let o=(i-r)/1e3;const n=e.curve&&e.curve.full_duration_s||0;return o<-1&&o+86400<=n&&(o+=86400),Math.max(0,Math.min(n,o))}_trimInputToOffset(t){if(t===""||t==null)return null;if(this._modal.timeMode==="clock")return this._clockToOffset(t);const e=pe(t,NaN);return Number.isFinite(e)?e:null}_toggleSplit(t){const e=this._modal,s=e.curve&&e.curve.full_duration_s||0,a=Math.max(20,s*.025),r=e.split.offsets,i=r.findIndex(o=>Math.abs(o-t)=0?r.splice(i,1):r.push(Math.round(t)),r.sort((o,n)=>o-n),e.split.profiles=[],this._render()}_wireCycleCanvas(t){const e=this._modal;if(!e||e.type!=="cycle-detail"||!e.loaded)return;const s=t.getElementById("wd-cyc-canvas");if(s)if(e.mode==="trim"){const a=t.getElementById("wd-trim-start"),r=t.getElementById("wd-trim-end");a&&a.addEventListener("input",()=>{const n=this._trimInputToOffset(a.value);n!==null&&(e.trim.start=Math.max(0,Math.min(n,e.trim.end-1)),this._drawCycleEditor())}),r&&r.addEventListener("input",()=>{const n=this._trimInputToOffset(r.value);n!==null&&(e.trim.end=Math.min(e.curve.full_duration_s,Math.max(n,e.trim.start+1)),this._drawCycleEditor())});const i=()=>{this._snapTrimBounds(),this._syncTrimInputs(),this._drawCycleEditor()};a&&a.addEventListener("change",i),r&&r.addEventListener("change",i);const o=()=>{e.drag&&(this._snapTrimBounds(),this._syncTrimInputs(),this._drawCycleEditor()),e.drag=null,s._wdOwnGesture=!1};s._wdAbortEdit=()=>{e.drag=null,s._wdOwnGesture=!1},s.addEventListener("pointerdown",n=>{const l=s._wd;if(!l)return;const c=s.getBoundingClientRect(),p=n.clientX-c.left,_=Math.abs(p-l.xToCss(e.trim.start)),f=Math.abs(p-l.xToCss(e.trim.end));if(n.pointerType==="touch"&&Math.min(_,f)>Tt){e.drag=null;return}e.drag=_<=f?"start":"end",s._wdOwnGesture=!0,s.setPointerCapture(n.pointerId)}),s.addEventListener("pointermove",n=>{if(!e.drag||s._wdPinching)return;const l=s._wd;if(!l)return;const c=s.getBoundingClientRect(),p=l.cssToX(n.clientX-c.left);e.drag==="start"?e.trim.start=Math.min(p,e.trim.end-1):e.trim.end=Math.max(p,e.trim.start+1),this._syncTrimInputs(),this._drawCycleEditor()}),s.addEventListener("pointerup",o),s.addEventListener("pointercancel",o)}else e.mode==="split"&&s.addEventListener("pointerdown",a=>{const r=s._wd;if(!r||s._wdPinching)return;const i=s.getBoundingClientRect();this._toggleSplit(r.cssToX(a.clientX-i.left))})}_wireSplitSegments(t){const e=this._modal;!e||e.type!=="cycle-detail"||e.mode!=="split"||t.querySelectorAll("[data-segidx]").forEach(s=>s.addEventListener("change",()=>{e.split.profiles[+s.dataset.segidx]=s.value||null}))}_wirePhaseInputs(t){const e=this._modal;!e||e.type!=="profile-panel"||e.tab!=="phases"||t.querySelectorAll("[data-phidx]").forEach(s=>{const a=()=>{const r=+s.dataset.phidx,i=s.dataset.phfield,o=e.phases[r];o&&(i==="name"?o.name=s.value:o[i]=Math.max(0,pe(s.value,0)*60),this._drawPhaseEditor())};s.addEventListener("input",a),s.addEventListener("change",a)})}_wirePhaseCanvas(t){const e=this._modal;if(!e||e.type!=="profile-panel"||e.tab!=="phases"||!e.env||!(e.env.avg||[]).length)return;const s=t.getElementById("wd-phase-canvas");if(!s)return;const a=e.env.target_duration||e.env.avg[e.env.avg.length-1][0],r=Math.max(5,a*.01),i=(n,l)=>{const c=s._wd;if(!c)return null;let p=null,_=l==="touch"?Tt:12;return(e.phases||[]).forEach((f,u)=>{[["start",f.start],["end",f.end]].forEach(([b,v])=>{const T=Math.abs(n-c.xToCss(v));T<_&&(_=T,p={idx:u,edge:b})})}),p},o=()=>{e.phaseDrag=null,s._wdOwnGesture=!1};s._wdAbortEdit=o,s.addEventListener("pointerdown",n=>{const l=s.getBoundingClientRect();e.phaseDrag=i(n.clientX-l.left,n.pointerType),e.phaseDrag&&(s._wdOwnGesture=!0,s.setPointerCapture(n.pointerId))}),s.addEventListener("pointermove",n=>{if(!e.phaseDrag||s._wdPinching)return;const l=s._wd;if(!l)return;const c=s.getBoundingClientRect(),p=l.cssToX(n.clientX-c.left),_=e.phases[e.phaseDrag.idx];_&&(e.phaseDrag.edge==="start"?_.start=Math.max(0,Math.min(p,_.end-r)):_.end=Math.min(a,Math.max(p,_.start+r)),this._syncPhaseInputs(e.phaseDrag.idx),this._drawPhaseEditor())}),s.addEventListener("pointerup",o),s.addEventListener("pointercancel",o)}_syncPhaseInputs(t){const e=this.shadowRoot,s=this._modal.phases[t];if(!s)return;const a=e.querySelector(`[data-phidx="${t}"][data-phfield="start"]`),r=e.querySelector(`[data-phidx="${t}"][data-phfield="end"]`);a&&(a.value=(s.start/60).toFixed(1)),r&&(r.value=(s.end/60).toFixed(1))}_wireCleanup(t){const e=this._modal;if(!e||e.type!=="profile-panel"||e.tab!=="cleanup"||!e.cleanup)return;t.querySelectorAll("[data-cleanidx]").forEach(a=>a.addEventListener("change",()=>{const r=e.cleanup.cycles[+a.dataset.cleanidx];if(!r)return;a.checked?e.cleanup.selected.add(r.cycle_id):e.cleanup.selected.delete(r.cycle_id);const i=e.cleanup.selected;if(!this._busy.has("pp-cleanup-del")&&!this._busy.has("pp-cleanup-unlabel")){const o=[["pp-cleanup-unlabel","btn.unlabel_selected",`Unlabel selected (${i.size})`],["pp-cleanup-del","btn.delete_selected",`Delete selected (${i.size})`]];for(const[n,l,c]of o){const p=t.querySelector(`[data-maction="${n}"]`);p&&(p.disabled=i.size===0,p.textContent=this._tText(l,{n:i.size},c))}}this._drawSpaghetti()})),t.querySelectorAll("tr[data-cid]").forEach(a=>{a.addEventListener("mouseenter",()=>{this._spagTableHoverCid=a.dataset.cid,this._drawSpaghetti()}),a.addEventListener("mouseleave",()=>{this._spagTableHoverCid=null,this._drawSpaghetti()})});const s=t.getElementById("wd-spag-canvas");s&&s.addEventListener("pointerdown",a=>{if(s._wdPinching)return;this._onGraphHoverInner(a.clientX,a.clientY,"wd-spag-canvas",{touch:a.pointerType==="touch"});const r=this._hoverNearest;if(r&&r.cid){const i=e.cleanup.selected;i.has(r.cid)?i.delete(r.cid):i.add(r.cid),this._render()}})}_onAction(t){const e=t.dataset.action,s=this.shadowRoot;if(e==="open-settings"){this._modal={type:"gear-settings",tab:this._gearTab||"prefs"},this._render();return}if(e==="retry-load"){this._loading=!0,this._fetchAll();return}const a=this._devices[this._selIdx];if(!a)return;const r=a.entry_id;if(e.startsWith("sug-"))return this._onActSuggestions(e,t,a,r,s);if(e.startsWith("ml-"))return this._onActMl(e,t,a,r);if(e.startsWith("store-"))return this._onActStore(e,t,a,r,s);if(e.startsWith("auto-"))return this._onActAuto(e,t,a,r,s);if(e.startsWith("maint-"))return this._onActMaintenance(e,t,a,r,s);if(e.startsWith("pg-")&&e!=="pg-new"&&e!=="pg-edit")return this._onActPlayground(e,t,a,r);if(e==="open-cycle"){const i=t.dataset.cid,o=t.dataset.mode==="review"?"review":"view";this._modal={type:"cycle-detail",entryId:r,cycleId:i,loaded:!1,mode:o,curve:null,ml:(this._mlById||{})[i]||null,trim:{start:0,end:0},split:{offsets:[],profiles:[]},drag:null},this._profiles.length||this._fetchProfiles(r),this._render(),this._ws({type:`${w}/get_cycle_power_data`,entry_id:r,cycle_id:i}).then(n=>{this._modal&&this._modal.cycleId===i&&(this._modal.curve=n,this._modal.loaded=!0,this._modal.trim={start:0,end:n.full_duration_s||0},this._loadCycleOverlays(r,i,n),this._loadCycleContext(r,i,n),this._render())}).catch(n=>this._showToast(this._tText("toast.could_not_load_cycle",{error:n.message||n},"Could not load cycle: "+(n.message||n)),"error"))}else if(e==="cleanup-edit-cycle"){const i=t.dataset.cid;this._prevModal=this._modal,this._modal={type:"cycle-detail",entryId:r,cycleId:i,loaded:!1,mode:"view",curve:null,ml:(this._mlById||{})[i]||null,trim:{start:0,end:0},split:{offsets:[],profiles:[]},drag:null},this._profiles.length||this._fetchProfiles(r),this._render(),this._ws({type:`${w}/get_cycle_power_data`,entry_id:r,cycle_id:i}).then(o=>{this._modal&&this._modal.cycleId===i&&(this._modal.curve=o,this._modal.loaded=!0,this._modal.trim={start:0,end:o.full_duration_s||0},this._loadCycleOverlays(r,i,o),this._loadCycleContext(r,i,o),this._render())}).catch(o=>this._showToast(this._tText("toast.could_not_load_cycle",{error:o.message||o},"Could not load cycle: "+(o.message||o)),"error"))}else if(e==="open-profile"){const i=t.dataset.pname;this._prevModal=null;const o=(this._profiles||[]).find(n=>n.name===i)||{name:i};this._modal={type:"profile-panel",name:i,tab:"stats",loaded:!1,stats:o,env:null,phases:[],catalog:[],cleanup:null},this._render(),Promise.all([this._ws({type:`${w}/get_profile_envelope`,entry_id:r,profile_name:i}).catch(()=>({envelope:null})),this._ws({type:`${w}/get_profile_phases`,entry_id:r,profile_name:i}).catch(()=>({phases:[]})),this._ws({type:`${w}/get_phase_catalog`,entry_id:r}).catch(()=>({phases:[]}))]).then(([n,l,c])=>{!this._modal||this._modal.name!==i||(this._modal.env=n.envelope,this._modal.phases=(l.phases||[]).map(p=>({name:p.name,start:p.start,end:p.end})),this._modal.catalog=(c.phases||[]).map(p=>p.name),this._modal.loaded=!0,this._render())})}else if(e==="create-profile")this._modal={type:"create-profile"},this._render();else if(e==="coverage-create")this._modal={type:"create-profile",prefillCycle:t.dataset.cid||"",prefillCycleMin:t.dataset.min||""},this._render();else if(e==="setup-cta"){const i=t.dataset.ctaAction||"";let o={};try{o=JSON.parse(t.dataset.ctaParams||"{}")}catch{}this._dispatchSetupCta(i,o)}else if(e==="setup-skip"){const i=t.dataset.step,o=t.dataset.snooze;if(i){let n;if(o==="never")n="never";else{const l=new Date;l.setDate(l.getDate()+14),n=l.toISOString()}this._setPref(i,n),this._reloadSetupStatus()}}else if(e==="hide-setup-card")this._setPref("setup_card_dismissed",!0),this._render();else if(e==="expand-setup")this._setPref("setup_card_dismissed",!1),this._render();else if(e==="set-settings-internal")this._snapshotFormToPending(s),this._setPref("show_internal",!!t.checked),this._render();else if(e==="set-settings-level"){const i=(t.type==="checkbox"?t.checked:t.dataset.slevel==="advanced")?"advanced":"basic";i!==this._pref("settings_level","basic")&&(this._snapshotFormToPending(s),this._setPref("settings_level",i),this._render())}else if(e==="pg-new"||e==="pg-edit"){if(e==="pg-new")this._modal={type:"profile-group",orig:null,name:"",members:[]};else{const i=t.dataset.gname,o=((this._profileGroups||{}).groups||[]).find(n=>n.name===i);this._modal={type:"profile-group",orig:i,name:i,members:o?[...o.members||[]]:[]}}this._render(),this._ensureProfileEnvs(r,(this._profiles||[]).map(i=>i.name)).then(()=>{this._modal&&this._modal.type==="profile-group"&&this._render()})}else if(e==="rebuild-envelopes")this._kickAndTrack({type:`${w}/rebuild_envelopes`,entry_id:r},"rebuild-envelopes",async()=>{this._showToast(this._t("toast.envelopes_rebuilt",{},"Envelopes rebuilt")),await this._fetchProfiles(r)});else if(e==="rec-start")this._ws({type:`${w}/start_recording`,entry_id:r}).then(()=>(this._showToast(this._t("toast.recording_started",{},"Recording started")),this._fetchRecState(r))).then(()=>this._render()).catch(i=>this._showToast(this._tText("toast.start_failed",{error:i.message||i},"Start failed: "+(i.message||i)),"error"));else if(e==="rec-stop")this._ws({type:`${w}/stop_recording`,entry_id:r}).then(()=>(this._showToast(this._t("toast.recording_stopped",{},"Recording stopped")),this._fetchRecState(r))).then(()=>this._render()).catch(i=>this._showToast(this._tText("toast.stop_failed",{error:i.message||i},"Stop failed: "+(i.message||i)),"error"));else if(e==="rec-process-open")this._fetchProfiles(r).then(()=>{this._modal={type:"process-recording"},this._render()});else if(e==="rec-discard")this._modal={type:"confirm",title:this._t("modal.discard_recording_title",{},"Discard Recording"),message:this._t("modal.discard_recording_msg",{},"Discard the saved recording? This cannot be undone."),okLabel:this._t("btn.discard",{},"Discard"),onOk:async()=>{try{await this._ws({type:`${w}/discard_recording`,entry_id:r}),this._showToast(this._t("toast.recording_discarded",{},"Recording discarded")),await this._fetchRecState(r)}catch(i){this._showToast(this._tText("toast.discard_failed",{error:i.message||i},"Discard failed: "+(i.message||i)),"error")}}},this._render();else if(e==="fb-confirm"){const i=t.dataset.cid;this._ws({type:`${w}/resolve_feedback`,entry_id:r,cycle_id:i,action:"confirm"}).then(()=>(this._showToast(this._t("toast.feedback_confirmed",{},"Feedback confirmed")),this._fetchFeedbacks(r))).then(()=>{const o=this._modal;o&&o.type==="cycle-detail"&&o.cycleId===i&&o.overlayPlan&&o.overlayPlan.suspected&&(o.curve&&(o.curve.profile_name=o.overlayPlan.name),o.overlayPlan={name:o.overlayPlan.name,suspected:!1,runnerUp:null}),this._render()}).catch(o=>this._showToast(this._tText("msg.toast_error",{error:o.message||o},"Error: "+(o.message||o)),"error"))}else if(e==="fb-ignore")this._ws({type:`${w}/resolve_feedback`,entry_id:r,cycle_id:t.dataset.cid,action:"ignore"}).then(()=>(this._showToast(this._t("toast.feedback_dismissed",{},"Feedback dismissed")),this._fetchFeedbacks(r))).then(()=>this._render()).catch(i=>this._showToast(this._tText("msg.toast_error",{error:i.message||i},"Error: "+(i.message||i)),"error"));else if(e==="fb-correct")this._fetchProfiles(r).then(()=>{this._modal={type:"correct-feedback",cycleId:t.dataset.cid,detectedProfile:t.dataset.prof},this._render()});else if(e==="fb-dismiss-all")this._modal={type:"confirm",title:this._t("modal.dismiss_all_title",{},"Dismiss All Feedbacks"),message:this._tText("modal.dismiss_all_msg",{count:this._feedbacks.length},`Dismiss all ${this._feedbacks.length} pending feedback requests?`),okLabel:this._t("modal.dismiss_all_ok",{},"Dismiss All"),onOk:async()=>{try{await this._ws({type:`${w}/dismiss_all_feedbacks`,entry_id:r}),this._showToast(this._t("toast.feedback_all_dismissed",{},"All feedbacks dismissed")),await this._fetchFeedbacks(r)}catch(i){this._showToast(this._tText("msg.toast_error",{error:i.message||i},"Error: "+(i.message||i)),"error")}}},this._render();else if(e==="create-phase")this._modal={type:"create-phase",deviceType:t.dataset.dtype},this._render();else if(e==="edit-phase")this._modal={type:"edit-phase",phaseId:t.dataset.pid,phaseName:t.dataset.pname,phaseDesc:t.dataset.pdesc,isDefault:t.dataset.pisdefault==="true"},this._render();else if(e==="del-phase"){const i=t.dataset.pname,o=t.dataset.pid,n=t.dataset.reset==="1",l=n?this._tText("modal.reset_phase_msg",{name:i},`Undo your edit of "${i}" and restore the built-in phase? Programs keep their ranges.`):this._tText("modal.delete_phase_msg",{name:i},`Delete phase "${i}"?`),c=n?this._tText("toast.phase_reset",{name:i},`"${i}" restored to the built-in phase`):this._tText("toast.phase_deleted",{name:i},`Phase "${i}" deleted`);this._modal={type:"confirm",title:n?this._t("modal.reset_phase_title",{},"Reset Phase"):this._t("modal.delete_phase_title",{},"Delete Phase"),message:l,okLabel:n?this._t("btn.reset",{},"Reset"):this._t("btn.delete",{},"Delete"),onOk:async()=>{try{await this._ws({type:`${w}/delete_phase`,entry_id:r,phase_id:o}),this._showToast(c),await this._fetchPhases(r)}catch(p){this._showToast(this._tText("msg.toast_delete_failed",{error:p.message||p},"Delete failed: "+(p.message||p)),"error")}}},this._render()}else if(e==="diag-refresh")this._fetchToolsData(r).then(()=>this._render());else if(e==="reprocess-history")this._modal={type:"confirm",title:this._t("modal.process_history_title",{},"Process History"),message:this._t("modal.process_history_msg",{},"Re-run matching, refresh suggestions, retrain ML (if enabled), recost cycles against recorded energy prices and recompute cycle health across all stored cycles. This may take a while."),okLabel:this._t("modal.process_history_ok",{},"Process"),onOk:()=>this._kickAndTrack({type:`${w}/reprocess_history`,entry_id:r},"reprocess",async i=>{const o=i.count||0,n=[this._tText("toast.processed_cycles",{n:o},o+" cycles")];i.suggestions!=null&&n.push(this._tText("toast.processed_suggestions",{n:i.suggestions},i.suggestions+" suggestion(s)"));const l=i.ml_training&&i.ml_training.ok&&(i.ml_training.promoted||[]).length||0;l&&n.push(this._tText("toast.processed_models",{n:l},l+" model(s) promoted")),this._showToast(this._tText("toast.processed",{bits:n.join(", ")},"Processed "+n.join(", "))),await this._fetchToolsData(r)})},this._render();else if(e==="clear-debug")this._modal={type:"confirm",title:this._t("modal.clear_debug_title",{},"Clear Debug Data"),message:this._t("modal.clear_debug_msg",{},"Delete all stored debug traces?"),okLabel:this._t("btn.clear",{},"Clear"),onOk:()=>this._busyRun("clear-debug",async()=>{try{const i=await this._ws({type:`${w}/clear_debug_data`,entry_id:r});this._showToast(this._tText("toast.debug_cleared",{count:i.count||0},`Cleared ${i.count||0} debug traces`)),await this._fetchToolsData(r)}catch(i){this._showToast(this._tText("msg.toast_error",{error:i.message||i},"Error: "+(i.message||i)),"error")}})},this._render();else if(e==="wipe-history")this._modal={type:"confirm",title:this._t("modal.wipe_all_title",{},"Wipe All Data"),message:this._t("modal.wipe_all_msg",{},"\u26A0\uFE0F This permanently deletes ALL cycles and profiles. This cannot be undone."),okLabel:this._t("modal.wipe_all_ok",{},"Wipe Everything"),onOk:()=>this._busyRun("wipe",async()=>{try{await this._ws({type:`${w}/wipe_history`,entry_id:r}),this._showToast(this._t("toast.all_wiped",{},"All data wiped")),this._cycles=[],this._profiles=[],await this._fetchToolsData(r)}catch(i){this._showToast(this._tText("msg.toast_error",{error:i.message||i},"Error: "+(i.message||i)),"error")}})},this._render();else if(e==="import-undo"){const i=this._importUndo||{};this._modal={type:"confirm",title:this._tText("modal.undo_import_title",{},"Undo last import"),message:this._tText("modal.undo_import_msg",{when:et(i.created_at)},`This device's data and settings go back to how they were before the last import (${et(i.created_at)}). Cycles recorded and changes made since then are lost.`),okLabel:this._tText("modal.undo_import_ok",{},"Undo import"),onOk:()=>this._busyRun("import-undo",async()=>{try{const o=await this._ws({type:`${w}/undo_import`,entry_id:r}),n=(o&&o.summary||{}).counts||{},l=(n.real_cycles||0)+(n.reference_cycles||0)+(n.backfill_cycles||0);this._showToast(this._tText("toast.import_undone",{profiles:n.profiles||0,cycles:l},`Import undone: ${n.profiles||0} profile(s) and ${l} cycle(s) restored`))}catch(o){this._showToast(this._tText("toast.import_undo_failed",{error:o.message||o},"Could not undo the import: "+(o.message||o)),"error")}this._isActiveEntry(r)&&(await this._fetchToolsData(r),await this._fetchCycles(r),await this._fetchProfiles(r))})},this._render()}else if(e==="export-config")this._ws({type:`${w}/export_config`,entry_id:r}).then(i=>{const o=new Blob([i.json_data],{type:"application/json"}),n=URL.createObjectURL(o),l=document.createElement("a");l.href=n,l.download=`washdata_export_${r.slice(0,8)}.json`,document.body.appendChild(l),l.click(),document.body.removeChild(l),URL.revokeObjectURL(n),this._showToast(this._t("toast.export_downloaded",{},"Export downloaded"))}).catch(i=>this._showToast(this._tText("toast.export_failed",{error:i.message||i},"Export failed: "+(i.message||i)),"error"));else if(e==="export-select-open")this._modal={type:"export-select",inventory:null,loading:!0,sel:this._wizEmptySel(),expanded:new Set},this._render(),(async()=>{let i=null;try{const o=await this._ws({type:`${w}/get_export_inventory`,entry_id:r});i=o&&o.manifest||null}catch(o){this._showToast(this._tText("toast.export_failed",{error:o.message||o},"Export failed: "+(o.message||o)),"error")}if(!(!this._isActiveEntry(r)||!this._modal||this._modal.type!=="export-select")){if(!i){this._modal=null,this._render();return}this._modal.inventory=i,this._modal.sel=this._wizInitSel({categories:i},!1),this._modal.loading=!1,this._render()}})();else if(e==="cyc-select-toggle")this._selectMode=!this._selectMode,this._selectMode||this._cycleSel.clear(),this._render();else if(e==="cyc-auto-open")this._openAutoLabelModal(r);else if(e==="cyc-merge"){const i=Array.from(this._cycleSel);if(i.length<2)return;this._fetchProfiles(r).then(()=>{this._modal={type:"merge-cycles",ids:i},this._render()})}else if(e==="cyc-relabel"){const i=Array.from(this._cycleSel);if(!i.length)return;this._fetchProfiles(r).then(()=>{this._modal={type:"bulk-relabel",ids:i},this._render()})}else if(e==="cyc-load-more")this._busyRun("cyc-load-more",async()=>{try{await this._loadMoreCycles(r)}catch(i){this._showToast(this._tText("toast.load_more_failed",{error:i.message||i},"Could not load more: "+(i.message||i)),"error")}});else if(e==="task-cancel"){const i=t.dataset.taskId;i&&(this._cancellingTasks.add(i),this._updateTaskPills(),this._ws({type:`${w}/cancel_task`,task_id:i}).catch(()=>{this._cancellingTasks.delete(i),this._updateTaskPills()}))}else if(e==="cyc-compare"){const i=Array.from(this._cycleSel);if(i.length<2)return;this._modal={type:"compare-cycles",ids:i,cycles:{},hidden:new Set,overlays:[],loaded:!1},this._profiles.length||this._fetchProfiles(r),this._render(),Promise.all(i.map(o=>this._ws({type:`${w}/get_cycle_power_data`,entry_id:r,cycle_id:o}).then(n=>({cid:o,r:n})).catch(()=>({cid:o,r:null})))).then(o=>{!this._modal||this._modal.type!=="compare-cycles"||(o.forEach(({cid:n,r:l})=>{l&&(this._modal.cycles[n]=l)}),this._modal.loaded=!0,this._render())})}else if(e==="cyc-bulk-del"){const i=Array.from(this._cycleSel);if(!i.length)return;this._deleteCyclesWithUndo(r,i)}else if(e==="retry-tab")this._fetchTabData();else if(e==="retry-cycles")this._fetchCycles(r).then(()=>this._render());else if(e==="retry-profiles")Promise.all([this._fetchProfiles(r),this._fetchProfileGroups(r)]).then(()=>this._render());else if(e==="retry-suggestions")this._fetchSuggestions(r).then(()=>this._render());else if(e==="goto-suggestions")this._settingsSugOnly=!0,this._tab="settings",this._fetchTabData();else if(e==="goto-conflicts")this._tab="settings",this._fetchTabData();else if(e==="goto-standby")this._settingsSec="detection",this._settingsSearch="",this._settingsSugOnly=!1,this._tab="settings",this._fetchTabData();else if(e==="conf-goto-section"){const i=this._conflictKeysFromOpts();for(const o of yt)if((o.fields||(o.groups||[]).flatMap(l=>l.fields||[])).some(l=>i.has(l.key))){this._settingsSec=o.id,this._render();break}}else if(e==="toggle-settings-history")this._settingsHistoryOpen=!this._settingsHistoryOpen,this._render();else if(e==="settings-revert-key"){const i=t.dataset.key,o=JSON.parse(t.dataset.val);if(!i)return;const n=a.entry_id;this._ws({type:`${w}/set_options`,entry_id:n,options:{[i]:o}}).then(()=>this._ws({type:`${w}/get_options`,entry_id:n})).then(l=>(this._opts=l.options||{},this._optDefaults=l.defaults||{},this._fetchSettingsChangelog(n))).then(()=>{this._showToast(this._tText("msg.toast_reverted",{key:this._t("setting."+i+".label",{},i)},"{key} reverted"),"success"),this._render()}).catch(l=>this._showToast(this._tText("msg.toast_error",{error:l.message||l},"Error: "+(l.message||l)),"error"))}else if(e==="toggle-log-drawer"){this._logOpen=!this._logOpen;try{localStorage.setItem("wd-log-open",this._logOpen?"1":"0")}catch{}this._render(),this._logOpen&&this._fetchLogs().then(()=>{this._logOpen&&this._render()})}else if(e==="add-device")this._navigate(`/config/integrations/integration/${w}`);else if(e==="goto-feedbacks")this._tab="history",this._historySub="cycles",this._cycleFilter={...this._cycleFilter,status:"needs_review"},this._fetchTabData();else if(e==="goto-recording")this._tab="status",this._fetchTabData();else if(e==="logs-refresh")this._fetchLogs().then(()=>this._render());else if(e==="logs-export")this._ws({type:`${w}/get_logs`,limit:500}).then(i=>{const o=(i.logs||[]).map(p=>`${new Date(p.ts*1e3).toISOString()} ${p.level} ${p.msg}`).join(`
`),n=new Blob([o],{type:"text/plain"}),l=URL.createObjectURL(n),c=document.createElement("a");c.href=l,c.download=`washdata_logs_${Date.now()}.txt`,document.body.appendChild(c),c.click(),document.body.removeChild(c),URL.revokeObjectURL(l),this._showToast(this._t("toast.logs_exported",{},"Logs exported"))}).catch(i=>this._showToast(this._tText("toast.export_failed",{error:i.message||i},"Export failed: "+(i.message||i)),"error"));else if(e==="import-config-open")this._modal={type:"import-wizard",step:"input",jsonText:"",manifest:null,error:null,sel:this._wizEmptySel(),expanded:new Set,mode:"merge",cycleDest:"reference",conflicts:{}},this._render();else if(e==="hist-import-open")this._modal={type:"history-import",step:"input",csvText:"",since:Ut(),token:null,scanTaskId:null,applyTaskId:null,result:null,accept:new Set,done:null,error:null},this._render();else if(e==="save-prefs"){const i=s.getElementById("wd-pref-tab")?.value||"",o=!!s.getElementById("wd-pref-debug")?.checked,n=s.getElementById("wd-pref-expected")?!!s.getElementById("wd-pref-expected").checked:!0,l=!!s.getElementById("wd-pref-raw")?.checked,c=s.getElementById("wd-pref-datefmt")?.value||"relative",p=s.getElementById("wd-pref-lang")?.value||"",_=parseFloat(s.getElementById("wd-pref-fontscale")?.value)||1,f={default_tab:i,show_debug:o,show_expected:n,show_raw:l,date_format:c,lang_override:p,font_scale:_};this._busyRun("save-prefs",async()=>{try{await this._ws({type:`${w}/set_user_prefs`,prefs:f}),this._panelCfg&&(this._panelCfg.prefs={...this._panelCfg.prefs||{},...f});const u=p||this._hass&&this._hass.locale&&this._hass.locale.language;await this._loadPanelLang(u),this._render(),this._showToast(this._t("toast.preferences_saved",{},"Preferences saved"))}catch(u){this._showToast(this._tText("toast.save_failed",{error:u.message||u},"Save failed: "+(u.message||u)),"error")}})}else if(e==="save-panel"){const i={default_tab:s.getElementById("wd-ps-deftab")?.value||"status",hidden_tabs:Array.from(s.querySelectorAll("[data-hidetab]")).filter(o=>o.checked).map(o=>o.dataset.hidetab)};this._busyRun("save-panel",async()=>{try{await this._ws({type:`${w}/set_panel_config`,panel:i}),this._panelCfg=await this._ws({type:`${w}/get_panel_config`}),this._tabInitialized=!0,this._applyPanelConfig(),this._showToast(this._t("toast.panel_settings_saved",{},"Panel settings saved"))}catch(o){this._showToast(this._tText("msg.toast_save_failed",{error:o.message||o},"Save failed: "+(o.message||o)),"error")}})}else if(e==="pause-cycle")this._ws({type:`${w}/pause_cycle`,entry_id:r}).then(i=>{if(i&&i.ok===!1){this._showToast(this._t("toast.pause_no_cycle",{},"No active cycle to pause"),"error");return}return this._showToast(this._t("toast.cycle_paused",{},"Cycle paused")),this._fetchAll()}).catch(i=>this._showToast(this._tText("toast.pause_failed",{error:i.message||i},"Pause failed: "+(i.message||i)),"error"));else if(e==="resume-cycle")this._ws({type:`${w}/resume_cycle`,entry_id:r}).then(i=>{if(i&&i.ok===!1){this._showToast(this._t("toast.resume_no_cycle",{},"No paused cycle to resume"),"error");return}return this._showToast(this._t("toast.cycle_resumed",{},"Cycle resumed")),this._fetchAll()}).catch(i=>this._showToast(this._tText("msg.toast_resume_failed",{error:i.message||i},"Resume failed: "+(i.message||i)),"error"));else if(e==="terminate-cycle")this._modal={type:"confirm",title:this._t("modal.force_stop_title",{},"Force Stop Cycle"),message:this._t("modal.force_stop_msg",{},"Force-stop the active cycle now? The cycle will be saved as interrupted."),okLabel:this._t("btn.force_stop",{},"Force Stop"),onOk:async()=>{try{await this._ws({type:`${w}/terminate_cycle`,entry_id:r}),this._showToast(this._t("toast.cycle_force_stopped",{},"Cycle force-stopped")),await this._fetchAll()}catch(i){this._showToast(this._tText("msg.toast_force_stop_failed",{error:i.message||i},"Force stop failed: "+(i.message||i)),"error")}}},this._render();else if(e==="save-rbac"){const i=!!s.getElementById("wd-rbac-enabled")?.checked,o=s.getElementById("wd-rbac-default")?.value||"none",n={};s.querySelectorAll("[data-rbacuser]").forEach(l=>{const c=l.dataset.rbacuser,p=l.dataset.rbacdev,_=l.value;n[c]||(n[c]={default:"none",devices:{}}),p==="__default__"?n[c].default=_:_&&_!=="inherit"&&(n[c].devices[p]=_)}),this._busyRun("save-rbac",async()=>{try{await this._ws({type:`${w}/set_panel_config`,rbac:{enabled:i,default_level:o,users:n}}),this._panelCfg=await this._ws({type:`${w}/get_panel_config`}),this._showToast(this._t("toast.access_saved",{},"Access control saved"))}catch(l){this._showToast(this._tText("msg.toast_save_failed",{error:l.message||l},"Save failed: "+(l.message||l)),"error")}})}}_applyAllSuggestions(t,e){this._busyRun("save-settings",async()=>{try{await this._ws({type:`${w}/apply_suggestions`,entry_id:t,keys:e}),this._showToast(this._t("toast.suggestions_applied",{},"Suggestions applied; integration reloading")),await this._fetchSuggestions(t);const s=await this._ws({type:`${w}/get_options`,entry_id:t});this._opts=s.options||{},this._optDefaults=s.defaults||{},this._prevOpts=null,this._cascadePending={},this._preCascadeOpts=null}catch(s){this._showToast(this._tText("toast.apply_failed",{error:s.message||s},"Apply failed: "+(s.message||s)),"error")}})}_onActSuggestions(t,e,s,a,r){if(t==="sug-apply-all"){const i=this._suggestions.map(n=>n.key),o=this._suggestions.map(n=>{const l=wt[n.key]||{},c=l.unit?` ${l.unit}`:"",p=n.current==null||n.current===""?"-":`${n.current}${c}`;return{text:`${this._t("setting."+n.key+".label",{},l.label||n.key)}: ${p} \u2192 ${n.suggested}${c}`,sub:we(this._t.bind(this),n.key,n.suggested,n.current)}});this._modal={type:"confirm",okPrimary:!0,items:o,title:this._t("modal.apply_suggestions_title",{},"Apply suggestions"),message:this._tText("modal.apply_suggestions_msg",{n:o.length},`Change these ${o.length} settings? The integration reloads after saving.`),okLabel:this._t("btn.apply_all",{},"Apply all"),onOk:()=>this._applyAllSuggestions(a,i)},this._render()}else t==="sug-show-all"?(this._settingsSugOnly=!1,this._render()):t==="sug-dismiss"?(this._settingsSugOnly=!1,this._busyRun("save-settings",async()=>{try{await this._ws({type:`${w}/clear_suggestions`,entry_id:a}),this._suggestions=[],this._showToast(this._t("toast.suggestions_dismissed",{},"Suggestions dismissed"))}catch(i){this._showToast(this._tText("toast.error",{error:i.message||i},"Error: "+(i.message||i)),"error")}})):t==="sug-unmute-all"?this._busyRun("save-settings",async()=>{try{const i=[...this._lockedSuggestions||[]],o=await Promise.allSettled(i.map(c=>this._ws({type:`${w}/set_suggestion_lock`,entry_id:a,key:c,locked:!1})));if(!this._isActiveEntry(a))return;const n=o.filter(c=>c.status==="rejected").length,l=o.slice().reverse().find(c=>c.status==="fulfilled");this._lockedSuggestions=l&&l.value&&l.value.locked_suggestions||[],await this._fetchSuggestions(a),n?this._showToast(this._tText("toast.error",{error:`${n} suggestion(s) failed to unlock`},`${n} suggestion(s) failed to unlock`),"error"):this._showToast(this._t("msg.sug_unmuted_all",{},"Muted suggestions reset"),"success")}catch(i){this._showToast(this._tText("toast.error",{error:i.message||i},"Error: "+(i.message||i)),"error")}}):t==="sug-analyze"&&this._busyRun("sug-analyze",async()=>{try{const i=await this._ws({type:`${w}/run_suggestion_analysis`,entry_id:a}),o=i&&i.count||0;this._showToast(o?this._tText("toast.analysis_complete",{count:o},`Analysis complete: ${o} suggestion(s)`):this._t("toast.analysis_complete_none",{},"Analysis complete: no new suggestions")),await this._fetchSuggestions(a)}catch(i){this._showToast(this._tText("toast.analysis_failed",{error:i.message||i},"Analysis failed: "+(i.message||i)),"error")}})}_onActMl(t,e,s,a){t==="ml-train-now"?this._kickAndTrack({type:`${w}/trigger_ml_training`,entry_id:a},"ml-train-now:"+a,async r=>{if(r&&r.ok){const i=(r.promoted||[]).length,o=!i&&(r.results||[]).find(n=>n&&n.promoted===!1&&n.reason_code);this._showToast(i?this._tText("toast.ml_training_promoted",{count:i},`Training complete: promoted ${i} model(s)`):o?this._mlSkipReason(o)[1]:this._t("toast.ml_training_no_improvement",{},"Training complete: baseline kept (no improvement)"))}else this._showToast(this._t("toast.ml_training_no_improvement",{},"Training complete: baseline kept (no improvement)"),"info");await this._loadMlTrainingStatus(a)}):t==="ml-revert-models"&&this._busyRun("ml-revert-models",async()=>{try{await this._ws({type:`${w}/revert_ml_models`,entry_id:a}),this._showToast(this._t("toast.models_reverted",{},"On-device models reverted to baseline")),await this._loadMlTrainingStatus(a)}catch(r){this._showToast(this._tText("msg.toast_revert_failed",{error:r.message||r},"Revert failed: "+(r.message||r)),"error")}})}_onActStore(t,e,s,a,r){if(t==="store-toggle-online"){const i=!!e.checked;this._busyRun("store-account",async()=>{try{const o=await this._ws({type:`${w}/store_set_online`,entry_id:a,enabled:i});this._constants.storeOnlineEnabled=!!(o&&o.enabled),this._constants.storeOnlineEnabled?(await this._loadStoreStatus(a),this._ensureStoreConnectListener()):(this._storeStatus={enabled:!1},this._storeConnected=!1)}catch(o){this._showToast(this._tText("toast.store_error",{error:o.message||o},"Error: "+(o.message||o)),"error")}})}else if(t==="store-toggle-pref"){const i=e.dataset.pref,o=!!e.checked;if(!i)return;this._busyRun("store-account",async()=>{try{const n=await this._ws({type:`${w}/store_set_prefs`,entry_id:a,prefs:{[i]:o}});n&&n.prefs&&(this._constants={...this._constants,storePrefs:n.prefs})}catch(n){this._showToast(this._tText("toast.store_error",{error:n.message||n},"Error: "+(n.message||n)),"error")}})}else if(t==="store-goto-identity")this._tab="settings",this._settingsSec="basic",this._fetchTabData(),requestAnimationFrame(()=>requestAnimationFrame(()=>{const i=this.shadowRoot&&this.shadowRoot.getElementById("wd-store-brand");i&&(i.focus(),i.scrollIntoView({block:"center"}))}));else if(t==="store-refresh-catalog")this._busyRun("store-refresh-catalog",async()=>{try{await this._ws({type:`${w}/store_refresh_catalog`,entry_id:a}),clearTimeout(this._brandSearchTimer),this._brandSearchTimer=null,this._catalog.brands=void 0,this._catalog.devices=void 0,this._catalog.forBrand=null,this._catalog.brandsFull=!1,this._catalog.brandPrefixes=[],this._dropModelCandidates(),this._catalogEntry=null,this._entityListCache&&(delete this._entityListCache.store_brand,delete this._entityListCache.store_model),this._showToast(this._t("toast.catalog_refreshed",{},"Community catalog refreshed"))}catch(i){this._showToast(this._tText("toast.store_error",{error:i.message||i},"Error: "+(i.message||i)),"error")}});else if(t==="store-connect"){const i=this._constants.storeWebOrigin;if(!i){this._showToast(this._t("toast.store_unavailable",{},"The community store is not available."),"error");return}this._ensureStoreConnectListener(),window.open(i+"/connect.html?origin="+encodeURIComponent(location.origin),"washdata_connect","width=480,height=640")}else if(t==="store-disconnect")this._busyRun("store-account",async()=>{try{await this._ws({type:`${w}/store_disconnect`,entry_id:a}),await this._loadStoreStatus(a),this._showToast(this._t("toast.store_disconnected",{},"Disconnected from the community store"))}catch(i){this._showToast(this._tText("toast.store_error",{error:i.message||i},"Error: "+(i.message||i)),"error")}});else if(t==="store-add-appliance"){const i=this._constants.storeWebOrigin;if(!i){this._showToast(this._t("toast.store_unavailable",{},"The community store is not available."),"error");return}this._ensureStoreConnectListener();const o=r.getElementById("wd-store-model"),n=r.getElementById("wd-store-brand"),l=new URLSearchParams({mode:"device",type:this._storeApplianceType(),brand:n&&n.value||this._opts.store_brand||"",model:o&&o.value||this._opts.store_model||"",origin:location.origin}).toString();this._storeContribEid=a,window.open(i+"/create.html?"+l,"washdata_create","width=560,height=760")}else if(t==="store-add-brand"){const i=this._constants.storeWebOrigin;if(!i){this._showToast(this._t("toast.store_unavailable",{},"The community store is not available."),"error");return}this._ensureStoreConnectListener();const o=r.getElementById("wd-store-brand"),n=new URLSearchParams({mode:"brand",brand:o&&o.value||this._opts.store_brand||"",origin:location.origin}).toString();this._storeContribEid=a,window.open(i+"/create.html?"+n,"washdata_create","width=560,height=760")}else if(t==="store-confirm-device"){const i=e.dataset.deviceId;this._busyRun("store-account",async()=>{try{const o=await this._ws({type:`${w}/store_confirm_device`,entry_id:a,device_id:i});if(o&&o.error){this._showToast(this._tText("toast.store_error",{error:o.error},"Error: "+o.error),"error");return}const n=[...this._catalog.devices||[],...this._storeDevices||[],this._catalogEntry&&this._catalogEntry.device||null];for(const l of n)o&&l&&String(l.id)===String(i)&&(l.confirmCount=o.confirmCount,l.status=o.status);this._showToast(o&&o.status==="approved"?this._t("toast.device_approved",{},"Approved by the community"):this._t("toast.thanks_confirming",{},"Thanks for confirming")),this._render()}catch(o){this._showToast(this._tText("toast.store_error",{error:o.message||o},"Error: "+(o.message||o)),"error")}})}else if(t==="store-rate-device"){const i=e.dataset.deviceId,o=parseInt(e.dataset.rating,10);if(!(o>=1&&o<=5))return;this._busyRun("store-account",async()=>{try{const n=await this._ws({type:`${w}/store_rate_device`,entry_id:a,device_id:i,rating:o});if(n&&n.error){this._showToast(this._tText("toast.store_error",{error:n.error},"Error: "+n.error),"error");return}this._showToast(this._t("toast.rating_saved",{},"Quality rating saved"))}catch(n){this._showToast(this._tText("toast.store_error",{error:n.message||n},"Error: "+(n.message||n)),"error")}})}else if(t==="store-nav"){const i=e.dataset.view;i==="brands"?(this._storeView="brands",this._storeDevice=null,this._storeProfile=null,this._render()):i==="device"&&(this._storeView="device",this._storeProfile=null,this._render())}else if(t==="store-open-device"){const i=e.dataset.deviceId,o=(this._storeDevices||[]).find(n=>String(n.id)===String(i));o&&this._storeOpenDevice(o)}else if(t==="store-open-profile"){const i=e.dataset.profileId,o=(this._storeProfiles||[]).find(n=>String(n.id)===String(i));if(!o)return;this._storeProfile=o,this._storeView="profile",this._storeCycles=[],this._storeLoading=!0,this._storeBrowseErr=!1,this._render(),this._ws({type:`${w}/store_get_cycles`,entry_id:a,profile_id:o.id}).then(n=>{!this._isActiveEntry(a)||this._storeView!=="profile"||(this._storeCycles=n&&n.items||[],this._storeBrowseErr=!!(n&&n.error))}).catch(()=>{this._isActiveEntry(a)&&(this._storeCycles=[],this._storeBrowseErr=!0)}).finally(()=>{this._isActiveEntry(a)&&(this._storeLoading=!1,this._render())})}else if(t==="store-onboard")this._storeQuery=(this._opts.store_brand||"").trim(),this._storeView="brands",this._storeDevice=null,this._storeProfile=null,this._tab="store",this._fetchTabData();else if(t==="store-toggle-dl-settings")this._dlSettings=!!e.checked;else if(t==="store-download-device"){const i=e.dataset.deviceId;if(!i)return;const o=!!this._dlSettings;this._busyRun("store-download-device",async()=>{try{const n=await this._ws({type:`${w}/store_download_device`,entry_id:a,device_id:i,include_settings:o}),l=n&&n.task_id?(await this._awaitTask(n.task_id)||{}).result:n;if(l&&(l.error||l.disabled)){const x=l.error||"unavailable";this._showToast(this._tText("toast.store_download_failed",{error:x},"Download failed: "+x),"error");return}const c=l&&l.profiles_adopted||0,p=l&&l.cycles_imported||0,_=l&&l.settings_applied||0,f=l&&l.cycles_skipped||0,u=f?this._tText("toast.store_download_skipped",{n:f},`${f} recording(s) skipped: too short, too gappy or already on your device.`):"";if(!c&&!p&&!_){this._showToast(u||this._t("toast.store_download_nothing",{},"Nothing new to download - this setup is already on your device."),"info");return}await this._fetchProfiles(a),await this._fetchCycles(a);const b=l&&l.phases_applied||0;let v;_?v=this._tText("toast.store_device_downloaded_settings",{p:c,c:p,ph:b,s:_},`${c} program(s), ${p} recording(s), ${b} phase map(s), ${_} setting(s) added`):b?v=this._tText("toast.store_device_downloaded_phases",{p:c,c:p,ph:b},`${c} program(s), ${p} recording(s), ${b} phase map(s) added`):v=this._tText("toast.store_device_downloaded",{p:c,c:p},`${c} program(s), ${p} recording(s) added`);const T=l&&l.partial&&l.failed_profiles||0,C=T?this._tText("toast.store_download_partial",{n:T},`${T} program(s) could not be downloaded; try again later.`):"";this._showToast([v,u,C].filter(Boolean).join(". "),T?"info":void 0)}catch(n){this._showToast(this._tText("toast.store_download_failed",{error:n.message||n},"Download failed: "+(n.message||n)),"error")}})}else if(t==="store-import"){const i=e.dataset.cycleId,o=this._storeProfile&&this._storeProfile.program||"";this._modal={type:"store-import",cycleId:i,program:o,mode:"new"},this._render()}else if(t==="store-share-cycle"){const i=e.dataset.cid,o=e.dataset.prof||"";this._modal={type:"store-share",cycleId:i,program:o,profiles:null,deviceId:null},this._render(),this._loadShareProfiles()}else if(t==="store-share-device")this._modal={type:"store-share-device",selected:new Set,includePhases:new Set,includeSettings:!1,loading:!0},this._render(),(async()=>{try{const o=await this._ws({type:`${w}/get_shareable_cycles`,entry_id:a});this._shareableCycles=o&&o.items||[],this._sharePhasePrograms=o&&o.phase_programs||[],this._shareAllPrograms=o&&o.all_programs||[]}catch{this._shareableCycles=[],this._sharePhasePrograms=[],this._shareAllPrograms=[]}if(!this._isActiveEntry(a)||!this._modal||this._modal.type!=="store-share-device")return;const i=new Set;this._shareableByProgram().forEach(o=>o.cycles.forEach(n=>i.add(n.id))),this._modal.selected=i,this._modal.includePhases=new Set(this._sharePhasePrograms),this._modal.loading=!1,this._render()})();else if(t==="store-share-profile"){const i=e.dataset.prog||"";this._modal={type:"store-share-device",selected:new Set,includePhases:new Set,includeSettings:!1,loading:!0,focusProfile:i},this._render(),(async()=>{try{const n=await this._ws({type:`${w}/get_shareable_cycles`,entry_id:a});this._shareableCycles=n&&n.items||[],this._sharePhasePrograms=n&&n.phase_programs||[],this._shareAllPrograms=n&&n.all_programs||[]}catch{this._shareableCycles=[],this._sharePhasePrograms=[],this._shareAllPrograms=[]}if(!this._isActiveEntry(a)||!this._modal||this._modal.type!=="store-share-device")return;const o=new Set;this._shareableByProgram().filter(n=>n.program===i).forEach(n=>n.cycles.forEach(l=>o.add(l.id))),this._modal.selected=o,this._modal.includePhases=new Set((this._sharePhasePrograms||[]).filter(n=>n===i)),this._modal.loading=!1,this._render()})()}else if(t==="store-share-add-profile"){const i=this._modal,o=this._constants.storeWebOrigin;if(!o||!i){o||this._showToast(this._t("toast.store_unavailable",{},"The community store is not available."),"error");return}this._ensureStoreConnectListener();const n=new URLSearchParams({mode:"profile",device:i.deviceId||"",type:this._storeApplianceType(),brand:this._opts.store_brand||"",model:this._opts.store_model||"",origin:location.origin}).toString();window.open(o+"/create.html?"+n,"washdata_create","width=560,height=760")}}_onActAuto(t,e,s,a,r){if(t==="auto-new")this._navigate("/config/automation/edit/new");else if(t==="auto-new-started")this._newAutomationFromEvent("started");else if(t==="auto-new-finished")this._newAutomationFromEvent("finished");else if(t==="auto-delete"){const i=e.dataset.autoid,o=e.dataset.autoname||"this automation";this._modal={type:"confirm",title:this._t("modal.delete_automation_title",{},"Delete Automation"),message:this._tText("modal.delete_automation_msg",{name:o},`Delete the automation "${o}" from Home Assistant? This cannot be undone.`),okLabel:this._t("btn.delete",{},"Delete"),onOk:async()=>{try{await this._hass.callApi("DELETE","config/automation/config/"+i),this._showToast(this._t("toast.automation_deleted",{},"Automation deleted")),await this._loadDeviceAutomations(a)}catch(n){this._showToast(this._tText("toast.delete_failed",{error:n.message||n},"Delete failed: "+(n.message||n)),"error")}}},this._render()}else t==="auto-convert-legacy"?this._convertLegacyActions():t==="auto-remove-legacy"&&(this._modal={type:"confirm",title:this._t("modal.remove_legacy_title",{},"Remove Legacy Actions"),message:this._t("modal.remove_legacy_msg",{},"Remove the legacy custom actions? They will stop firing on cycle events. This cannot be undone from the panel."),okLabel:this._t("btn.remove",{},"Remove"),onOk:async()=>{try{await this._ws({type:`${w}/set_options`,entry_id:a,options:{notify_actions:[]}}),this._opts={...this._opts,notify_actions:[]},this._showToast(this._t("toast.legacy_removed",{},"Legacy actions removed"))}catch(i){this._showToast(this._tText("toast.remove_failed",{error:i.message||i},"Remove failed: "+(i.message||i)),"error")}}},this._render())}_onActMaintenance(t,e,s,a,r){if(t==="maint-add"){const i=r.getElementById("wd-maint-type")?.value||"",o=r.getElementById("wd-maint-date")?.value||"",n=(r.getElementById("wd-maint-notes")?.value||"").trim();if(!i){this._showToast(this._tText("toast.maint_add_failed",{error:this._t("lbl.event_type",{},"Event type")},"Could not add event: Event type"),"error");return}this._busyRun("maint-add",async()=>{try{const l={type:`${w}/add_maintenance_event`,entry_id:a,event_type:i};o&&(l.date=o),n&&(l.notes=n),await this._ws(l),await this._fetchMaintenance(a),this._showToast(this._t("toast.maint_added",{},"Maintenance event added")),this._render()}catch(l){this._showToast(this._tText("toast.maint_add_failed",{error:l.message||l},"Could not add event: "+(l.message||l)),"error")}})}else if(t==="maint-delete"){const i=e.dataset.mid;this._modal={type:"confirm",title:this._t("modal.delete_maintenance_title",{},"Delete Maintenance Event"),message:this._t("modal.delete_maintenance_msg",{},"Delete this maintenance record? This cannot be undone."),okLabel:this._t("btn.delete",{},"Delete"),onOk:()=>this._busyRun("maint-delete",async()=>{try{await this._ws({type:`${w}/delete_maintenance_event`,entry_id:a,event_id:i}),await this._fetchMaintenance(a),this._showToast(this._t("toast.maint_deleted",{},"Maintenance event deleted"))}catch(o){this._showToast(this._tText("toast.maint_delete_failed",{error:o.message||o},"Could not delete event: "+(o.message||o)),"error")}})},this._render()}else if(t==="maint-save-odometer"){const i=r.getElementById("wd-maint-odometer")?.value,o=parseInt(i,10);if(isNaN(o)||o<0){this._showToast(this._tText("toast.odometer_save_failed",{error:this._t("lbl.total_cycles_run",{},"cycles run in total")},"Could not save total: cycles run in total"),"error");return}this._busyRun("maint-save-odometer",async()=>{try{await this._ws({type:`${w}/set_lifetime_cycle_count`,entry_id:a,count:o}),await this._fetchMaintenance(a),this._showToast(this._t("toast.odometer_saved",{},"Total cycles saved")),this._render()}catch(n){this._showToast(this._tText("toast.odometer_save_failed",{error:n.message||n},"Could not save total: "+(n.message||n)),"error")}})}else if(t==="maint-save-reminders"){const i=this._maintenance||{},o=i.reminders||{},n={};r.querySelectorAll("[data-maint-rem]").forEach(_=>{const f=_.dataset.maintRem,u=parseInt(_.value,10);n[f]=!isNaN(u)&&u>0?u:0});const l=Object.keys(n).some(_=>n[_]!==(parseInt(o[_],10)||0)),c=[];for(const _ of Array.isArray(i.custom_tasks)?i.custom_tasks:[]){const f=r.querySelector(`[data-mtask-name="${CSS.escape(_.id)}"]`);if(!f)continue;const u=f.value.trim();if(!u){this._showToast(this._tText("toast.task_name_required",{},"Give the task a name"),"error");return}const b=this._maintInterval(r.querySelector(`[data-mtask-cycles="${CSS.escape(_.id)}"]`)),v=this._maintInterval(r.querySelector(`[data-mtask-days="${CSS.escape(_.id)}"]`)),T={};u!==_.name&&(T.name=u),b!==(_.cycles||0)&&(T.cycles=b),v!==(_.days||0)&&(T.days=v),Object.keys(T).length&&c.push({task_id:_.id,...T})}const p=this._maintNewTaskInput(r);this._busyRun("maint-save-reminders",async()=>{try{l&&await this._ws({type:`${w}/set_options`,entry_id:a,options:{maintenance_reminder_cycles:n}});for(const _ of c)await this._ws({type:`${w}/update_maintenance_task`,entry_id:a,..._});p.name&&await this._maintAddTask(a,p),await this._fetchMaintenance(a),this._showToast(this._t("toast.reminders_saved",{},"Service reminders saved")),this._render()}catch(_){await this._fetchMaintenance(a).catch(()=>{}),this._render(),this._showToast(this._tText("toast.reminders_save_failed",{error:_.message||_},"Could not save reminders: "+(_.message||_)),"error")}})}else if(t==="maint-task-add"){const i=this._maintNewTaskInput(r);if(!i.name){this._showToast(this._tText("toast.task_name_required",{},"Give the task a name"),"error");return}this._busyRun("maint-task-add",async()=>{try{await this._maintAddTask(a,i),await this._fetchMaintenance(a),this._showToast(this._t("toast.task_added",{},"Task added")),this._render()}catch(o){this._showToast(this._tText("toast.task_save_failed",{error:o.message||o},"Could not save task: "+(o.message||o)),"error")}})}else if(t==="maint-task-remove"){const i=e.dataset.mtask,o=(this._maintenance&&this._maintenance.custom_tasks||[]).find(l=>l.id===i),n=o?o.name:i;this._modal={type:"confirm",title:this._t("modal.remove_task_title",{},"Remove Task"),message:this._tText("modal.remove_task_msg",{task:n},'Remove the reminder "{task}"? Its entries in the maintenance log are kept.'),okLabel:this._t("btn.remove",{},"Remove"),onOk:()=>this._busyRun("maint-task-remove",async()=>{try{await this._ws({type:`${w}/delete_maintenance_task`,entry_id:a,task_id:i}),await this._fetchMaintenance(a),this._showToast(this._t("toast.task_removed",{},"Task removed"))}catch(l){this._showToast(this._tText("toast.task_save_failed",{error:l.message||l},"Could not save task: "+(l.message||l)),"error")}})},this._render()}else if(t==="maint-log-done"){const i=e.dataset.mtype;if(!i)return;const o=this._maintLabel(i);this._busyRun("maint-log-done",async()=>{try{await this._ws({type:`${w}/add_maintenance_event`,entry_id:a,event_type:i}),await this._fetchMaintenance(a),this._showToast(this._tText("toast.maint_logged",{task:o},"Logged as done: {task}")),this._render()}catch(n){this._showToast(this._tText("toast.maint_add_failed",{error:n.message||n},"Could not add event: "+(n.message||n)),"error")}})}}_maintInterval(t){const e=String(t&&t.value||"").trim();if(!e)return 0;const s=Number(e);return isNaN(s)?e:s}_maintNewTaskInput(t){return{name:(t.getElementById("wd-mtask-new-name")?.value||"").trim(),cycles:this._maintInterval(t.getElementById("wd-mtask-new-cycles")),days:this._maintInterval(t.getElementById("wd-mtask-new-days"))}}async _maintAddTask(t,e){const s={type:`${w}/add_maintenance_task`,entry_id:t,name:e.name};return e.cycles&&(s.cycles=e.cycles),e.days&&(s.days=e.days),this._ws(s)}_onActPlayground(t,e,s,a){if(t==="pg-analysis-tab"){const r=e.dataset.subtab||"history";r!==this._pgAnalysisTab&&(this._pgAnalysisTab=r,this._render(),requestAnimationFrame(()=>this._drawPlaygroundCanvases()))}else if(t==="pg-run-history")this._pgRunHistory();else if(t==="pg-batch-cancel"){this._pgBatchCancel=!0;const r=this._pgHistoryTaskId||this._pgSweepTaskId;r&&this._ws({type:`${w}/cancel_task`,task_id:r}).catch(()=>{})}else if(t==="pg-load-run"){const r=e.dataset.taskId;if(!r)return;const i=this._tasks[r];if(!i||i.kind!=="pg_history"&&i.kind!=="pg_sweep"){this._showToast(this._t("toast.pg_run_gone",{},"That run is no longer available."),"info");return}const o=i.entry_id;this._ws({type:`${w}/get_task_result`,task_id:r}).then(n=>{if(!this._isActiveEntry(o))return;const l=n&&n.result;if(!l){this._showToast(this._t("toast.pg_run_gone",{},"That run is no longer available."),"info");return}i.kind==="pg_history"?(this._pgHistory=l,this._pgAnalysisTab="history"):(this._pgSweepNew=l.error?null:l,this._pgAnalysisTab="sweep"),this._render()}).catch(()=>this._showToast(this._t("toast.pg_run_gone",{},"That run is no longer available."),"info"))}else t==="pg-open-cycle"?this._pgSelectCycle(e.dataset.cid):t==="pg-sweep-run2"?this._pgRunSweep2():t==="pg-sweep-apply2"?this._pgApplySweepValue(e.dataset.val):t==="pg-run"||t==="pg-load"?this._pgLoad():t==="pg-rerun"?this._pgRerunDetail():t==="pg-cancel-run"?this._pgCancelRun():t==="pg-reset-params"?(this._pgThreshStart=null,this._pgThreshStop=null,this._pgParamOverrides={},this._render(),requestAnimationFrame(()=>this._pgDrawCanvas())):t==="pg-apply-settings"?this._pgApplyToSettings():t==="pg-load-live"?this._pgLoadLive():t==="pg-load-suggested"?this._pgLoadSuggested():t==="pg-preset-save"?this._pgSavePreset():t==="pg-preset-load"?this._pgLoadPreset():t==="pg-preset-delete"?this._pgDeletePreset():t==="pg-publish-one"&&this._pgPublishOne(e.dataset.pgkey)}async _onModalAction(t,e){const s=this.shadowRoot,a=this._devices[this._selIdx],r=a?a.entry_id:null,i=this._modal;if(t==="cancel"){if(i&&i.type==="cycle-detail"&&this._prevModal){const o=this._devices[this._selIdx];o?await this._closeCycleDetail(o.entry_id):(this._modal=null,this._render())}else this._modal=null,this._render();return}if(t==="ok"&&i&&i.onOk){const o=i.onOk;this._modal=null,this._render(),await o(),this._render();return}if(i&&i.type==="profile-group"){if(t==="pg-save"){const o=s.getElementById("wd-pg-name")?.value?.trim(),n=Array.from(s.querySelectorAll(".wd-pg-mem")).filter(l=>l.checked).map(l=>l.value);if(!o){this._showToast(this._t("toast.group_name_required",{},"Group name is required"),"error");return}if(n.length<2){this._showToast(this._t("toast.min_2_profiles",{},"Select at least 2 profiles for a group"),"error");return}await this._busyRun("pg-save",async()=>{try{i.orig&&i.orig!==o&&await this._ws({type:`${w}/rename_profile_group`,entry_id:r,name:i.orig,new_name:o}),await this._ws({type:`${w}/save_profile_group`,entry_id:r,name:o,members:n}),this._showToast(this._t("toast.group_saved",{},"Group saved")),this._modal=null,await this._fetchProfileGroups(r)}catch(l){this._showToast(this._tText("msg.toast_save_failed",{error:l.message||l},"Save failed: "+(l.message||l)),"error")}});return}if(t==="pg-delete"&&i.orig){await this._busyRun("pg-save",async()=>{try{await this._ws({type:`${w}/delete_profile_group`,entry_id:r,name:i.orig}),this._showToast(this._t("toast.group_deleted",{},"Group deleted")),this._modal=null,await this._fetchProfileGroups(r)}catch(o){this._showToast(this._tText("msg.toast_delete_failed",{error:o.message||o},"Delete failed: "+(o.message||o)),"error")}});return}}if(t.startsWith("store-import-")||t==="store-share-ok"||t.startsWith("wiz-")||t.startsWith("imp-")||t==="import-back"||t==="import-analyze"||t==="import-apply-ok")return this._onMActImport(t,e,i,r,s);if(t.startsWith("hist-"))return this._onMActHistoryImport(t,e,i,r,s);if(t.startsWith("sd-")||t==="store-share-device-ok")return this._onMActStoreShare(t,e,i,r);if(i&&i.type==="export-select"&&t==="export-generate"&&r){const o=this._wizSelectionPayload(i);await this._busyRun("export-select",async()=>{try{const n=await this._ws({type:`${w}/export_config_selective`,entry_id:r,selection:o}),l=new Blob([n.json_data],{type:"application/json"}),c=URL.createObjectURL(l),p=document.createElement("a");p.href=c,p.download=`washdata_export_${r.slice(0,8)}.json`,document.body.appendChild(p),p.click(),document.body.removeChild(p),URL.revokeObjectURL(c),this._modal=null,this._showToast(this._t("toast.export_selective_done",{},"Export downloaded"))}catch(n){this._showToast(this._tText("toast.export_failed",{error:n.message||n},"Export failed: "+(n.message||n)),"error")}});return}if(t.startsWith("cyc-")||t.startsWith("trim-mode-"))return this._onMActCycleDetail(t,i,r,s);if(i&&i.type==="profile-panel"){if(t.indexOf("pp-tab-")===0){const o=t.slice(7);i.tab=o,this._render(),o==="cleanup"&&!i.cleanup&&this._ws({type:`${w}/get_profile_cycles`,entry_id:r,profile_name:i.name}).then(n=>{this._modal&&this._modal.name===i.name&&(this._modal.cleanup={cycles:n.cycles||[],selected:new Set},this._render())}).catch(()=>{this._modal&&(this._modal.cleanup={cycles:[],selected:new Set},this._render())});return}return this._onMActProfilePanel(t,e,i,r,s)}if(t==="label-ok"&&r){const o=s.getElementById("wd-label-profile"),n=o?o.value:"",l=n||null,c=n==="__create_new__"&&s.getElementById("wd-new-profile-name")?.value?.trim()||null;this._modal=null;try{await this._ws({type:`${w}/label_cycle`,entry_id:r,cycle_id:i.cycleId,profile_name:l||null,new_profile_name:c}),this._showToast(this._t("toast.cycle_labelled",{},"Cycle labelled")),await this._fetchCycles(r),await this._fetchProfiles(r),await this._fetchFeedbacks(r)}catch(p){this._showToast(this._tText("toast.label_failed",{error:p.message||p},"Label failed: "+(p.message||p)),"error")}this._render()}else if(t==="create-profile-ok"&&r){const o=s.getElementById("wd-cp-name")?.value?.trim(),n=s.getElementById("wd-cp-cycle")?.value||null,l=parseFloat(s.getElementById("wd-cp-dur")?.value||0);if(this._modal=null,!o){this._showToast(this._t("toast.profile_name_required",{},"Profile name is required"),"error"),this._render();return}const c=!n&&l>0?l:null;try{await this._ws({type:`${w}/create_profile`,entry_id:r,name:o,reference_cycle:n||null,manual_duration_min:c}),this._showToast(this._tText("toast.profile_created",{name:o},`Profile "${o}" created`)),await this._fetchProfiles(r)}catch(p){this._showToast(this._tText("toast.create_failed",{error:p.message||p},"Create failed: "+(p.message||p)),"error")}this._render()}else if(t==="create-phase-ok"&&r){const o=s.getElementById("wd-ph-name")?.value?.trim(),n=s.getElementById("wd-ph-desc")?.value?.trim()||"";if(this._modal=null,!o){this._showToast(this._t("toast.phase_name_required",{},"Phase name is required"),"error"),this._render();return}try{await this._ws({type:`${w}/create_phase`,entry_id:r,device_type:i.deviceType||"",name:o,description:n}),this._showToast(this._tText("toast.phase_created",{name:o},`Phase "${o}" created`)),await this._fetchPhases(r)}catch(l){this._showToast(this._tText("msg.toast_create_failed",{error:l.message||l},"Create failed: "+(l.message||l)),"error")}this._render()}else if(t==="edit-phase-ok"&&r){const o=s.getElementById("wd-eph-name")?.value?.trim(),n=s.getElementById("wd-eph-desc")?.value?.trim()||"";if(this._modal=null,!o){this._showToast(this._t("toast.name_required",{},"Name required"),"error"),this._render();return}try{await this._ws({type:`${w}/update_phase`,entry_id:r,phase_id:i.phaseId,new_name:o,description:n}),this._showToast(this._t("toast.phase_updated",{},"Phase updated")),await this._fetchPhases(r)}catch(l){this._showToast(this._tText("toast.update_failed",{error:l.message||l},"Update failed: "+(l.message||l)),"error")}this._render()}else if(t==="process-rec-ok"&&r){const o=s.getElementById("wd-pr-mode")?.value;let n=s.getElementById("wd-pr-profile")?.value?.trim();o==="existing_profile"&&(n=s.getElementById("wd-pr-profile-sel")?.value||n);const l=parseFloat(s.getElementById("wd-pr-head")?.value||0),c=parseFloat(s.getElementById("wd-pr-tail")?.value||0);if(this._modal=null,!n){this._showToast(this._t("msg.toast_profile_name_required",{},"Profile name is required"),"error"),this._render();return}try{await this._ws({type:`${w}/process_recording`,entry_id:r,profile_name:n,save_mode:o,head_trim:l,tail_trim:c}),this._showToast(this._t("toast.recording_saved",{},"Recording saved to profile")),await this._fetchRecState(r),await this._fetchProfiles(r)}catch(p){this._showToast(this._tText("msg.toast_save_failed",{error:p.message||p},"Save failed: "+(p.message||p)),"error")}this._render()}else if(t==="correct-fb-ok"&&r){const o=s.getElementById("wd-fb-profile")?.value,n=parseFloat(s.getElementById("wd-fb-dur")?.value||0)||null;this._modal=null;try{await this._ws({type:`${w}/resolve_feedback`,entry_id:r,cycle_id:i.cycleId,action:"correct",corrected_profile:o,corrected_duration_min:n}),this._showToast(this._t("toast.correction_submitted",{},"Correction submitted")),await this._fetchFeedbacks(r)}catch(l){this._showToast(this._tText("msg.toast_error",{error:l.message||l},"Error: "+(l.message||l)),"error")}this._render()}else if(t==="auto-run"&&r){const o=parseFloat(s.getElementById("wd-al-thr")?.value||""),n={type:`${w}/auto_label_cycles`,entry_id:r};Number.isFinite(o)&&(n.confidence_threshold=Math.min(.95,Math.max(.5,o))),this._modal=null,this._render(),await this._busyRun("auto-label",async()=>{try{const l=await this._ws(n),c=l&&l.task_id?await this._awaitTask(l.task_id):null;this._showToast(this._autoLabelToast(c)),await this._fetchCycles(r)}catch(l){this._showToast(this._tText("msg.toast_auto_label_failed",{error:l.message||l},"Auto-label failed: "+(l.message||l)),"error")}})}else if(t==="merge-ok"&&r){const o=s.getElementById("wd-merge-prof")?.value||"",n=s.getElementById("wd-merge-newname")?.value?.trim()||null,l=i.ids||[];this._modal=null,this._render(),this._kickAndTrack({type:`${w}/apply_merge`,entry_id:r,cycle_ids:l,target_profile:o||null,new_profile_name:n},"cyc-merge",async()=>{this._showToast(this._t("toast.cycles_merged",{},"Cycles merged")),this._cycleSel.clear(),this._selectMode=!1,await this._fetchCycles(r),await this._fetchProfiles(r)})}else if(t==="bulk-relabel-ok"&&r){const o=s.getElementById("wd-relabel-profile")?.value||"",n=(i.ids||[]).slice();if(this._modal=null,this._render(),!n.length)return;await this._busyRun("cyc-relabel",async()=>{try{for(const l of n)await this._ws({type:`${w}/label_cycle`,entry_id:r,cycle_id:l,profile_name:o||null});this._showToast(this._tText("toast.relabel_done",{count:n.length},`Relabelled ${n.length} cycle(s)`)),this._cycleSel.clear(),this._selectMode=!1,await this._fetchCycles(r),await this._fetchProfiles(r),await this._fetchFeedbacks(r)}catch(l){this._showToast(this._tText("toast.relabel_failed",{error:l.message||l},"Relabel failed: "+(l.message||l)),"error")}})}}async _histUpload(t,e){const s=await this._ws({type:`${w}/history_import_begin`,entry_id:t}),a=Math.max(4096,s.chunk_bytes||512*1024);let r=0,i=0;for(;ii&&(o=n+1)}await this._ws({type:`${w}/history_import_chunk`,entry_id:t,token:s.token,seq:r,text:e.slice(i,o)}),r+=1,i=o}return s.token}async _histStartScan(t,e,s){e.token=s,e.step="scan",e.error=null,this._render();const a=await this._ws({type:`${w}/start_history_import_scan`,entry_id:t,token:s});e.scanTaskId=a.task_id,this._addProvisionalTask(a.task_id,"history_import",t,0),this._tasksSubscribed||this._pollTaskGeneric(a.task_id),this._histAdopt(a.task_id)}async _histAdopt(t){if(!t)return;let e=(this._tasks||{})[t];if(!e||e.state==="running")try{e=await this._ws({type:`${w}/get_task_result`,task_id:t})}catch{return}e&&e.state&&e.state!=="running"&&this._histTaskFinished(e)}async _histTaskFinished(t){const e=this._modal;if(!(!e||e.type!=="history-import")&&!(t.id!==e.scanTaskId&&t.id!==e.applyTaskId)&&(this._histSettled=this._histSettled||new Set,!this._histSettled.has(t.id))){if(this._histSettled.add(t.id),t.id===e.scanTaskId){if(t.state==="error"){e.step="input",e.error=t.error||this._t("msg.hist_scan_failed",{},"Scanning failed."),this._render();return}try{const s=t.result?t:await this._ws({type:`${w}/get_task_result`,task_id:t.id});e.result=s.result||{},e.accept=new Set((e.result.segments||[]).filter(a=>a.accept).map(a=>a.index)),e.step="review",this._render(),requestAnimationFrame(()=>this._drawHistorySparklines())}catch(s){e.step="input",e.error=String(s&&s.message||s),this._render()}return}if(t.id===e.applyTaskId){if(t.state==="error"){e.error=t.error==="scan_expired"?this._t("msg.hist_scan_expired",{},"That scan is no longer available. Scan again."):t.error||this._t("msg.hist_import_failed",{},"Import failed."),e.step="review",this._render();return}try{const a=t.result?t:await this._ws({type:`${w}/get_task_result`,task_id:t.id});e.done=a.result||{}}catch{e.done={}}e.step="done",this._render();const s=this._devices[this._selIdx];s&&(this._fetchCycles(s.entry_id),this._fetchProfiles(s.entry_id))}}}async _onMActHistoryImport(t,e,s,a,r){if(!(!s||s.type!=="history-import"||!a)){if(t==="hist-scan"){const i=r.getElementById("wd-hist-csv"),o=i?i.value:s.csvText||"";if(!o.trim()){this._showToast(this._t("toast.hist_csv_required",{},"Load a CSV file or paste its contents first"),"error");return}s.csvText=o,await this._busyRun("hist-import",async()=>{try{const n=await this._histUpload(a,o);await this._histStartScan(a,s,n)}catch(n){s.error=String(n&&n.message||n),this._render()}});return}if(t==="hist-recorder"){const i=r.getElementById("wd-hist-since"),o=i&&i.value||Ut();s.since=o,await this._busyRun("hist-import",async()=>{try{const n=await this._ws({type:`${w}/history_import_recorder`,entry_id:a,start_date:o});if(!n.rows){s.error=this._t("msg.hist_recorder_empty",{},"Home Assistant has no detailed history for this sensor in that window."),this._render();return}await this._histStartScan(a,s,n.token)}catch(n){s.error=String(n&&n.message||n),this._render()}});return}if(t==="hist-cancel-scan"){if(s.scanTaskId)try{await this._ws({type:`${w}/cancel_task`,task_id:s.scanTaskId})}catch{}s.step="input",s.scanTaskId=null,this._render();return}if(t==="hist-back"){s.step="input",s.result=null,s.error=null,this._render();return}if(t==="hist-toggle-all"){const i=s.result&&s.result.segments||[],o=i.every(n=>s.accept.has(n.index));s.accept=o?new Set:new Set(i.map(n=>n.index)),this._render(),requestAnimationFrame(()=>this._drawHistorySparklines());return}if(t==="hist-apply"){if(!s.accept||!s.accept.size)return;await this._busyRun("hist-apply",async()=>{try{const i=await this._ws({type:`${w}/apply_history_import`,entry_id:a,scan_task_id:s.scanTaskId,accept:[...s.accept].sort((o,n)=>o-n)});s.applyTaskId=i.task_id,this._addProvisionalTask(i.task_id,"history_import_apply",a,s.accept.size),this._tasksSubscribed||this._pollTaskGeneric(i.task_id),this._histAdopt(i.task_id)}catch(i){s.error=String(i&&i.message||i),this._render()}});return}if(t==="hist-goto-cycles"){this._modal=null,this._tab="history",this._historySub="cycles",this._cycleFilter={...this._cycleFilter||{},status:"imported"},this._fetchTabData();return}}}async _onMActImport(t,e,s,a,r){if(s&&s.type==="store-import"){if(t==="store-import-mode-new"){s.mode="new",this._render();return}if(t==="store-import-mode-merge"){s.mode="merge",this._render();return}if(t==="store-import-ok"){const i={type:`${w}/store_import_cycle`,entry_id:a,cycle_id:s.cycleId};if(s.mode==="merge"){const o=r.getElementById("wd-store-import-target")?.value||"";if(!o){this._showToast(this._t("toast.store_pick_profile",{},"Pick a profile to merge into"),"error");return}i.target_profile=o}else{const o=(r.getElementById("wd-store-import-name")?.value||"").trim()||s.program;if(!o){this._showToast(this._t("toast.store_name_required",{},"Enter a profile name"),"error");return}i.new_profile_name=o}await this._busyRun("store-import",async()=>{try{const o=await this._ws(i);if(o&&o.error==="low_quality"){this._showToast(this._t("toast.store_import_low_quality",{},"This recording is too short, too gappy or implausible to import."),"error");return}if(o&&o.error==="duplicate"){this._showToast(this._t("toast.store_import_duplicate",{},"This recording is already on your device."),"info");return}if(o&&o.error){this._showToast(this._tText("toast.store_import_failed",{error:o.error},"Import failed: "+o.error),"error");return}this._modal=null,this._showToast(this._tText("toast.store_imported",{profile:o&&o.profile||""},`Imported into ${o&&o.profile||"profile"}`)),await this._fetchProfiles(a)}catch(o){this._showToast(this._tText("toast.store_import_failed",{error:o.message||o},"Import failed: "+(o.message||o)),"error")}});return}}if(s&&s.type==="store-share"&&t==="store-share-ok"){const i=(r.getElementById("wd-store-share-prog")?.value||"").trim(),o=(r.getElementById("wd-store-share-desc")?.value||"").trim();if(!i){this._showToast(this._t("toast.store_pick_profile_share",{},"Pick a profile to share into"),"error");return}await this._busyRun("store-share",async()=>{try{const n=await this._ws({type:`${w}/store_upload_cycle`,entry_id:a,local_cycle_id:s.cycleId,program:i,description:o});if(n&&n.error){if(n.error==="no_appliance_declared")this._showToast(this._t("toast.store_no_appliance",{},"Set your appliance brand and model in Settings first."),"error");else{const l=n.detail?`${n.error} - ${n.detail}`:n.error;this._showToast(this._tText("toast.store_share_failed",{error:l},"Share failed: "+l),"error")}return}this._modal=null,this._showToast(this._t("toast.store_shared",{},"Shared to the community store - pending review."))}catch(n){this._showToast(this._tText("toast.store_share_failed",{error:n.message||n},"Share failed: "+(n.message||n)),"error")}});return}if(s&&(s.type==="export-select"||s.type==="import-wizard")){const i=s.type==="export-select"?{categories:s.inventory||{}}:s.manifest||{categories:{}};if(t==="wiz-toggle-all"){const o=i.categories||{},n=s.type==="import-wizard";let l=!0;this._wizCatOrder().filter(c=>o[c]&&o[c].present).forEach(c=>{n&&o[c].importable===!1||this._wizCatState(s,c,i).state!=="all"&&(l=!1)}),s.sel=l?this._wizEmptySel():this._wizInitSel(i,n),this._render();return}if(t==="wiz-toggle-cat"){const o=e.dataset.cat,l=this._wizCatState(s,o,i).state!=="all";if(o==="profiles"){const c=i.categories.profiles&&i.categories.profiles.items||[];s.sel.profiles=new Set(l?c.map(p=>p.name):[])}else if(this._wizCycleCats().includes(o)){const c=this._wizIdSet(s.sel,o);c.clear(),l&&(i.categories[o]&&i.categories[o].groups||[]).forEach(p=>p.cycles.forEach(_=>{_.id!=null&&c.add(String(_.id))}))}else l?s.sel.cats.add(o):s.sel.cats.delete(o);this._render();return}if(t==="wiz-toggle-profile"){const o=e.dataset.name;s.sel.profiles.has(o)?s.sel.profiles.delete(o):s.sel.profiles.add(o),this._render();return}if(t==="wiz-toggle-cycgroup"){const o=e.dataset.cat,n=e.dataset.prof,l=this._wizIdSet(s.sel,o),c=this._wizGroupIds(i,o,n),p=c.length>0&&c.every(_=>l.has(_));c.forEach(_=>{p?l.delete(_):l.add(_)}),this._render();return}if(t==="wiz-toggle-cyc"){const o=e.dataset.cat,n=e.dataset.cid,l=this._wizIdSet(s.sel,o);l.has(n)?l.delete(n):l.add(n),this._render();return}if(t==="wiz-expand"){const o=e.dataset.key;s.expanded||(s.expanded=new Set),s.expanded.has(o)?s.expanded.delete(o):s.expanded.add(o),this._render();return}}if(s&&s.type==="import-wizard"){if(t==="import-back"){s.step="input",s.error=null,this._render();return}if(t==="imp-mode-merge"){s.mode="merge",this._render();return}if(t==="imp-mode-replace"){s.mode="replace",this._render();return}if(t==="imp-dest-reference"){s.cycleDest="reference",this._render();return}if(t==="imp-dest-real"){(s.manifest||{}).real_history_allowed!==!1&&(s.cycleDest="real_history",this._render());return}if(t==="imp-conflict"){s.conflicts[e.dataset.prof]=e.value;return}if(t==="import-analyze"&&a){const i=r.getElementById("wd-import-json"),o=i?i.value:s.jsonText||"";if(s.jsonText=o,!o.trim()){this._showToast(this._t("toast.json_required",{},"JSON data is required"),"error");return}s.step="analyze",s.error=null,this._render();try{const n=await this._ws({type:`${w}/analyze_import`,entry_id:a,json_data:o});if(!this._isActiveEntry(a)||!this._modal||this._modal.type!=="import-wizard")return;const l=n&&n.manifest||{};if(l.error){s.step="input",s.error=l.error,this._render();return}s.manifest=l,s.sel=this._wizInitSel(l,!0),s.conflicts={},(l.categories&&l.categories.profiles&&l.categories.profiles.items||[]).forEach(c=>{c.conflict&&(s.conflicts[c.name]="import_as_copy")}),s.step="select",this._render()}catch(n){if(!this._isActiveEntry(a)||!this._modal||this._modal.type!=="import-wizard")return;s.step="input",s.error=n&&n.message||String(n),this._render()}return}if(t==="import-apply-ok"&&a){const i=this._wizSelectionPayload(s);await this._busyRun("import-wizard",async()=>{try{const o=await this._ws({type:`${w}/import_config_selective`,entry_id:a,json_data:s.jsonText,selection:i,mode:s.mode,conflict_resolutions:s.conflicts,cycle_destination:s.cycleDest,apply_settings:!0}),n=o&&o.summary||{};this._modal=null;const l=(n.real_cycles_imported||0)+(n.reference_cycles_imported||0)+(n.backfill_cycles_imported||0);(s.mode==="replace"||Object.values(s.conflicts||{}).includes("overwrite"))&&n.restore_point_saved===!1?this._showToast(this._tText("toast.import_no_restore_point",{},"Imported, but no restore point could be saved, so this import cannot be undone"),"error"):this._showToast(this._tText("toast.import_selective_done",{profiles:n.profiles_imported||0,cycles:l},`Imported ${n.profiles_imported||0} profile(s) and ${l} cycle(s)`)),await this._fetchCycles(a),await this._fetchProfiles(a),n.restore_point_saved&&await this._fetchToolsData(a)}catch(o){this._showToast(this._tText("toast.import_failed",{error:o.message||o},"Import failed: "+(o.message||o)),"error")}});return}}}async _onMActStoreShare(t,e,s,a){if(s&&s.type==="store-share-device"){if(t==="sd-toggle-cyc"){const r=e.dataset.cid;s.selected.has(r)?s.selected.delete(r):s.selected.add(r),this._render();return}if(t==="sd-toggle-prof"){const r=e.dataset.prog,i=this._shareableByProgram().find(o=>o.program===r);if(i){const o=i.cycles.every(n=>s.selected.has(n.id));i.cycles.forEach(n=>{o?s.selected.delete(n.id):s.selected.add(n.id)})}this._render();return}if(t==="sd-toggle-phases"){const r=e.dataset.prog;s.includePhases||(s.includePhases=new Set),s.includePhases.has(r)?s.includePhases.delete(r):s.includePhases.add(r),this._render();return}if(t==="sd-toggle-settings"){s.includeSettings=!s.includeSettings,this._render();return}if(t==="sd-toggle-consent"){s.consented=!s.consented,this._render();return}if(t==="sd-toggle-guide"){s.guideOpen=!s.guideOpen,this._render();return}if(t==="store-share-device-ok"){const r=new Map;(this._shareableCycles||[]).forEach(l=>r.set(l.id,(l.profile_name||"").trim()));const i=Array.from(s.selected).map(l=>({local_cycle_id:l,program:r.get(l)||""})).filter(l=>l.program);if(!i.length){this._showToast(this._t("toast.share_device_none_sel",{},"Select at least one cycle to share"),"error");return}const o=new Set(i.map(l=>l.program)),n=Array.from(s.includePhases||[]).filter(l=>o.has(l));await this._busyRun("store-share-device",async()=>{try{const l=await this._ws({type:`${w}/store_upload_device`,entry_id:a,items:i,include_phases:n,include_settings:!!s.includeSettings});if(l&&l.error){if(l.error==="no_appliance_declared")this._showToast(this._t("toast.store_no_appliance",{},"Set your appliance brand and model in Settings first."),"error");else{const u=l.detail?`${l.error} - ${l.detail}`:l.error;this._showToast(this._tText("toast.store_share_failed",{error:u},"Share failed: "+u),"error")}return}const c=l&&l.cycle_ids&&l.cycle_ids.length||0,p=l&&l.errors&&l.errors.length||0,_=l&&l.duplicates||0,f=l&&l.created!=null?l.created:c;if(!c){const u=l&&l.errors&&l.errors[0]||l&&l.detail||"upload_failed";this._showToast(this._tText("toast.store_share_failed",{error:u},"Share failed: "+u),"error");return}this._modal=null,p?this._showToast(this._tText("toast.store_device_shared_partial",{n:c,failed:p},`Shared ${c} cycle(s); ${p} could not be uploaded.`),"info"):_&&!f?this._showToast(this._tText("toast.store_device_shared_all_dup",{n:_},`All ${_} cycle(s) were already in the community store.`),"info"):_?this._showToast(this._tText("toast.store_device_shared_some_dup",{created:f,dup:_},`Shared ${f} cycle(s); ${_} were already in the store.`)):this._showToast(this._tText("toast.store_device_shared",{n:f},`Shared ${f} cycle(s) to the community store - pending review.`))}catch(l){this._showToast(this._tText("toast.store_share_failed",{error:l.message||l},"Share failed: "+(l.message||l)),"error")}});return}}}async _onMActCycleDetail(t,e,s,a){if(e&&e.type==="cycle-detail"){if(t==="cyc-view"){e.mode="view",this._render();return}if(t==="cyc-trim"){e.mode="trim",(!e.trim||e.trim.end<=0)&&(e.trim={start:0,end:e.curve&&e.curve.full_duration_s||0}),this._render();return}if(t==="cyc-split"){e.mode="split",this._render();return}if(t==="cyc-review"){e.mode="review",this._render();return}if(t==="cyc-ctx"){const r=Number(a.getElementById("wd-cyc-ctx")?.value),i=e.entryId||s;if(!Ft.includes(r)||e.ctx&&e.ctx.minutes===r)return;const o=this._pref("cycle_context_min",null);this._setPref("cycle_context_min",{...o&&typeof o=="object"?o:{},[i]:r}),delete this._canvasZoom["wd-cyc-canvas"],this._loadCycleContext(i,e.cycleId,e.curve),this._render();return}if(t==="cyc-review-save"){const r=e.cycleId,i=a.getElementById("wd-cyc-rev-quality")?.value||"",o=!!a.getElementById("wd-cyc-rev-golden")?.checked,n=a.getElementById("wd-cyc-rev-notes")?.value||"",l=Array.from(a.querySelectorAll(".wd-cyc-rev-tag")).filter(_=>_.checked).map(_=>_.value),c=a.getElementById("wd-cyc-rev-label")?.value??"",p=e.curve&&e.curve.profile_name||"";await this._busyRun("cyc-review-save",async()=>{try{await this._ws({type:`${w}/set_ml_review`,entry_id:s,cycle_id:r,quality:i,golden:o,tags:l,notes:n}),c!==p&&await this._ws({type:`${w}/label_cycle`,entry_id:s,cycle_id:r,profile_name:c||null}),this._showToast(this._t("toast.review_saved",{},"Review saved")),await this._fetchCycles(s),c!==p&&await this._fetchFeedbacks(s),await this._loadMlIndex(s);const _=this._modal;_&&_.type==="cycle-detail"&&_.cycleId===r&&(_.ml=(this._mlById||{})[r]||_.ml,delete _.reviewLabel,c!==p&&_.curve&&(_.curve.profile_name=c||null,_.profileEnv=null,_.runnerUpEnv=null,this._loadCycleOverlays(s,r,_.curve)))}catch(_){this._showToast(this._tText("msg.toast_save_failed",{error:_.message||_},"Save failed: "+(_.message||_)),"error")}});return}if(t==="trim-mode-s"){e.timeMode="s",this._render();return}if(t==="trim-mode-clock"){e.timeMode="clock",this._render();return}if(t==="cyc-reset-trim"){e.trim={start:0,end:e.curve&&e.curve.full_duration_s||0},this._render();return}if(t==="cyc-clear-split"){e.split={offsets:[],profiles:[]},this._render();return}if(t==="cyc-label"){this._profiles.length||await this._fetchProfiles(s),this._modal={type:"label-cycle",cycleId:e.cycleId},this._render();return}if(t==="cyc-delete"){const r=e.cycleId;this._modal=null,this._render(),this._deleteCyclesWithUndo(s,[r]);return}if(t==="cyc-auto-split"){const r=parseInt(a.getElementById("wd-split-gap")?.value||"900",10);await this._busyRun("cyc-auto",async()=>{try{const i=await this._ws({type:`${w}/analyze_split`,entry_id:s,cycle_id:e.cycleId,gap_seconds:r});e.split.offsets=(i.split_offsets||[]).slice(),e.split.profiles=[],e.split.offsets.length||this._showToast(this._t("toast.no_split_found",{},"No idle gaps found to split on"),"info")}catch(i){this._showToast(this._tText("toast.auto_detect_failed",{error:i.message||i},"Auto-detect failed: "+(i.message||i)),"error")}});return}if(t==="cyc-apply-trim"){const r=e.cycleId,i=e.trim.start,o=e.trim.end,n=e.curve&&e.curve.full_duration_s||0,l=n>0?Math.max(0,Math.round((o-i)/n*100)):100;if(l<50&&!confirm(this._tText("msg.trim_destructive_confirm",{pct:l},`This keeps only ${l}% of the cycle and cannot be undone. Continue?`)))return;this._kickAndTrack({type:`${w}/trim_cycle`,entry_id:s,cycle_id:r,start_s:i,end_s:o},"cyc-trim-apply",async()=>{this._showToast(this._t("toast.cycle_trimmed",{},"Cycle trimmed")),await this._closeCycleDetail(s),await this._fetchCycles(s)});return}if(t==="cyc-apply-split"){const r=e.cycleId,i=e.split.offsets.slice(),o=e.split.profiles.slice();this._kickAndTrack({type:`${w}/apply_split`,entry_id:s,cycle_id:r,split_offsets:i,segment_profiles:o},"cyc-split-apply",async n=>{this._showToast(this._tText("toast.split_complete",{count:(n.new_ids||[]).length},`Split into ${(n.new_ids||[]).length} cycles`)),await this._closeCycleDetail(s),await this._fetchCycles(s),await this._fetchProfiles(s)});return}}}async _onMActProfilePanel(t,e,s,a,r){if(s&&s.type==="profile-panel"){if(t==="pp-phase-add"){const i=s.env&&s.env.target_duration||(s.env&&s.env.avg&&s.env.avg.length?s.env.avg[s.env.avg.length-1][0]:600),o=s.phases.length?s.phases[s.phases.length-1].end:0,n=Math.min(o,i);s.phases.push({name:s.catalog[0]||"",start:n,end:Math.min(n+Math.max(60,i*.1),i)}),this._render();return}if(t==="pp-phase-rm"){const i=+(e&&e.dataset.idx||-1);i>=0&&(s.phases.splice(i,1),this._render());return}if(t==="pp-phase-save"){const i=s.phases.filter(o=>o.name).map(o=>({name:o.name,start:o.start,end:o.end}));await this._busyRun("pp-phase-save",async()=>{try{await this._ws({type:`${w}/set_profile_phases`,entry_id:a,profile_name:s.name,phases:i}),this._showToast(this._t("toast.phases_saved",{},"Phases saved"))}catch(o){this._showToast(this._tText("msg.toast_save_failed",{error:o.message||o},"Save failed: "+(o.message||o)),"error")}});return}if(t==="pp-cleanup-del"){const i=s.cleanup?Array.from(s.cleanup.selected):[];if(!i.length)return;await this._busyRun("pp-cleanup-del",async()=>{try{for(const n of i)await this._ws({type:`${w}/delete_cycle`,entry_id:a,cycle_id:n});this._showToast(this._tText("toast.cycles_deleted",{count:i.length},`Deleted ${i.length} cycle(s)`));const o=await this._ws({type:`${w}/get_profile_cycles`,entry_id:a,profile_name:s.name});this._modal&&(this._modal.cleanup={cycles:o.cycles||[],selected:new Set}),await this._fetchProfiles(a)}catch(o){this._showToast(this._tText("msg.toast_delete_failed",{error:o.message||o},"Delete failed: "+(o.message||o)),"error")}});return}if(t==="pp-cleanup-unlabel"){const i=s.cleanup?Array.from(s.cleanup.selected):[];if(!i.length)return;await this._busyRun("pp-cleanup-unlabel",async()=>{try{for(const n of i)await this._ws({type:`${w}/label_cycle`,entry_id:a,cycle_id:n,profile_name:null});this._showToast(this._tText("toast.unlabel_done",{count:i.length},`Unlabelled ${i.length} cycle(s)`));const o=await this._ws({type:`${w}/get_profile_cycles`,entry_id:a,profile_name:s.name});this._modal===s&&(s.cleanup={cycles:o.cycles||[],selected:new Set}),await this._fetchProfiles(a),await this._fetchCycles(a),await this._fetchFeedbacks(a)}catch(o){this._showToast(this._tText("toast.unlabel_failed",{error:o.message||o},"Unlabel failed: "+(o.message||o)),"error")}});return}if(t==="pp-rename"){const i=r.getElementById("wd-pp-rename")?.value?.trim(),o=r.getElementById("wd-pp-dur"),n=parseFloat(o?.value||"0"),l=!!o&&!o.disabled&&n>0&&n!==parseFloat(o.dataset.orig||"0");if(!i){this._showToast(this._t("msg.toast_name_required",{},"Name required"),"error");return}try{await this._ws({type:`${w}/rename_profile`,entry_id:a,profile_name:s.name,new_name:i,manual_duration_min:l?n:null}),this._showToast(this._t("toast.profile_renamed",{},"Profile renamed")),s.name=i,await Promise.all([this._fetchProfiles(a),this._fetchProfileGroups(a)]),s.stats=(this._profiles||[]).find(c=>c.name===i)||s.stats,this._render()}catch(c){this._showToast(this._tText("toast.rename_failed",{error:c.message||c},"Rename failed: "+(c.message||c)),"error")}return}if(t==="pp-rebuild"){this._kickAndTrack({type:`${w}/rebuild_envelopes`,entry_id:a},"pp-rebuild",async()=>{try{const i=await this._ws({type:`${w}/get_profile_envelope`,entry_id:a,profile_name:s.name});this._modal&&(this._modal.env=i.envelope)}catch{}this._showToast(this._t("toast.envelope_rebuilt",{},"Envelope rebuilt"))});return}if(t==="pp-delete"){this._deleteProfileWithUndo(a,s.name);return}}}_snapshotFormToPending(t){t&&t.querySelectorAll("#wd-settings-form [data-opt], #wd-ml-form [data-opt]").forEach(e=>{const s=e.dataset.opt;if(!this._dirtyOptKeys.has(s))return;const a=wt[s],r=a&&a.type||e.dataset.ftype||"text";if(e.type==="checkbox"){this._pendingSettings[s]=e.checked;return}if(r==="checkboxlist"){this._pendingSettings[s]=this._collectCheckboxlist(e,s);return}if(r==="entitylist"){this._pendingSettings[s]=Array.from(e.querySelectorAll(".wd-pill")).map(i=>i.dataset.val).filter(Boolean);return}if(r==="timerlist"){this._pendingSettings[s]=Array.from(e.querySelectorAll(".wd-timer-row")).map(i=>({offset_minutes:parseFloat(i.querySelector('[data-field="offset_minutes"]').value)||0,message:(i.querySelector('[data-field="message"]').value||"").trim(),auto_pause:i.querySelector('[data-field="auto_pause"]').checked})).filter(i=>i.offset_minutes>0);return}if(r==="number"){const i=String(e.value).trim();if(i===""){a&&a.clearable?this._pendingSettings[s]=null:delete this._pendingSettings[s];return}const o=parseFloat(i);isNaN(o)||(this._pendingSettings[s]=o);return}if(r==="list"){this._pendingSettings[s]=String(e.value).split(",").map(i=>i.trim()).filter(Boolean);return}if(r==="intlist"){this._pendingSettings[s]=me(e.value);return}if(r==="json"){const i=String(e.value).trim();if(!i){this._pendingSettings[s]=[];return}try{this._pendingSettings[s]=JSON.parse(i)}catch{}return}if(r==="entity"||r==="device"){const i=String(e.value).trim();this._pendingSettings[s]=i||null;return}this._pendingSettings[s]=e.value})}_collectCheckboxlist(t,e){const s=Array.from(t.querySelectorAll("[data-choice]")).filter(r=>r.checked).map(r=>r.dataset.choice);if(s.length)return s;const a=wt[e];return a&&Array.isArray(a.def)&&a.def.length?a.def.slice():s}_conflictKeysForOpts(t,e){const s=Object.assign({},e||{},t),a=this._conflictCtx(),r=new Set;for(const i of ee)if(i.check(s,a))for(const o of Object.keys(i.fieldErrors(s,a)))r.add(o);return r}_conflictCtx(){const t=this._hass&&this._hass.states?this._hass.states:{};return{stateOf:e=>e&&t[e]||null}}_conflictCountForOpts(t,e){return this._conflictKeysForOpts(t,e).size}_conflictKeysFromOpts(){return this._conflictKeysForOpts(this._editedOpts(),this._optDefaults)}_readSettingsFormValues(t){const e=Object.assign({},this._optDefaults,this._opts,this._pendingSettings);return t&&t.querySelectorAll("#wd-settings-form [data-opt]").forEach(s=>{const a=s.dataset.opt;if(s.type==="checkbox"){e[a]=s.checked;return}if(s.dataset.ftype==="checkboxlist"){e[a]=this._collectCheckboxlist(s,a);return}if(s.dataset.ftype==="entity"||s.dataset.ftype==="device"){e[a]=String(s.value).trim()||null;return}const r=parseFloat(s.value);isNaN(r)?s.value!==""&&(e[a]=s.value):e[a]=r}),e}_liveValidateSettings(t){if(!t)return{};const e=this._readSettingsFormValues(t),s=t.getElementById("wd-settings-form");if(!s)return{};const a={};for(const n of this._suggestions||[])n.key!=null&&n.suggested!=null&&(a[n.key]=+n.suggested);const r={},i=this._conflictCtx();for(const n of ee){if(!n.check(e,i))continue;const l=n.fieldErrors(e,i);for(const[c,p]of Object.entries(l)){const _=a[c],f=_!=null&&!n.check({...e,[c]:_},i)?{...p,suggFix:_}:p;(r[c]=r[c]||[]).push(f)}}const o={};for(const n of Ae)if(n.check(e,i))for(const[l,c]of Object.entries(n.fieldNotes(e,i)))(o[l]=o[l]||[]).push(c);return s.querySelectorAll("[data-cnote]").forEach(n=>{const l=o[n.dataset.cnote];if(!l||!l.length){n.hidden=!0,n.innerHTML="";return}n.hidden=!1,n.innerHTML=l.map(c=>d(this._tText(c.msgKey,c.msgVars,c.msgFb))).join(" ")}),s.querySelectorAll("[data-cerr]").forEach(n=>{const l=n.dataset.cerr,c=r[l],p=s.querySelector(`.wd-field[data-field="${l}"]`);!c||!c.length?(n.hidden=!0,n.innerHTML="",p&&p.classList.remove("wd-has-conflict")):(n.hidden=!1,p&&p.classList.add("wd-has-conflict"),n.innerHTML=c.map(_=>{const f=this._tText(_.msgKey,_.msgVars,_.msgFb);let u="";if(_.suggFix!=null){const b=+_.suggFix.toFixed(2);u=`${this._t("conflict.suggestion_resolves",{val:b},`Stage the pending suggestion (${b}) below to fix this`)} `}else if(_.fixVal!=null&&!isNaN(+_.fixVal)){const b=Number.isInteger(_.fixVal)?_.fixVal:+_.fixVal.toFixed(2);u=`${this._t("conflict.use_fix",{val:b},`Use ${b}`)} `}return`\u26A0 ${d(f)}${u}
`}).join(""))}),r}_cascadeConflictFix(t,e,s){const a=new Set;for(let r=0;r<10;r++){const i=this._liveValidateSettings(t);let o=!1;for(const[n,l]of Object.entries(i)){if(n===s)continue;const c=l.find(_=>_.fixVal!=null&&!isNaN(+_.fixVal)&&+_.fixVal>0);if(!c)continue;const p=e.querySelector(`[data-opt="${n}"]`);p?(p.value=c.fixVal,this._dirtyOptKeys.add(n)):(this._preCascadeOpts==null&&(this._preCascadeOpts=JSON.parse(JSON.stringify(this._opts||{}))),this._opts={...this._opts,[n]:c.fixVal},(this._cascadePending??={})[n]=c.fixVal),a.add(n),o=!0;break}if(!o)break}if(this._liveValidateSettings(t),this._snapshotFormToPending(t),a.size>0){const r=a.size,i=r>1?"s":"";this._showToast(this._tText("conflict.cascade_toast",{n:r},`Other settings adjusted for consistency: ${r}`),"success")}}_changedOptions(t){const e=this._preCascadeOpts||this._opts||{},s={};for(const[a,r]of Object.entries(t))if(JSON.stringify(r)!==JSON.stringify(e[a])&&!(!(a in e)&&(r===""||r===null))){if(!(a in e)&&typeof r=="boolean"){const i=this._optDefaults&&a in this._optDefaults?this._optDefaults[a]:(wt[a]||{}).def;if(i!==void 0&&JSON.stringify(r)===JSON.stringify(i))continue}s[a]=r}return s}async _saveSettings(){const t=this.shadowRoot,e=this._devices[this._selIdx];if(!e)return;const s=Object.assign({},this._pendingSettings,this._cascadePending);this._invalidJson=null,t.querySelectorAll("[data-opt]").forEach(n=>{const l=n.dataset.opt,c=wt[l],p=c&&c.type||n.dataset.ftype||"text";if(n.type==="checkbox"){s[l]=n.checked;return}if(p==="checkboxlist"){s[l]=this._collectCheckboxlist(n,l);return}if(p==="entitylist"){s[l]=Array.from(n.querySelectorAll(".wd-pill")).map(f=>f.dataset.val).filter(Boolean);return}if(p==="timerlist"){s[l]=Array.from(n.querySelectorAll(".wd-timer-row")).map(f=>({offset_minutes:parseFloat(f.querySelector('[data-field="offset_minutes"]').value)||0,message:(f.querySelector('[data-field="message"]').value||"").trim(),auto_pause:f.querySelector('[data-field="auto_pause"]').checked})).filter(f=>f.offset_minutes>0);return}const _=n.value;if(p==="number"){const f=String(_).trim();if(f===""){c&&c.clearable?s[l]=null:delete s[l];return}const u=parseFloat(f);isNaN(u)||(s[l]=u);return}if(p==="list"){s[l]=String(_).split(",").map(f=>f.trim()).filter(Boolean);return}if(p==="intlist"){s[l]=me(_);return}if(p==="json"){const f=String(_).trim();if(!f){s[l]=[];return}try{s[l]=JSON.parse(f)}catch{this._invalidJson=l}return}if(p==="entity"||p==="device"){const f=String(_).trim();s[l]=f||null;return}s[l]=_});const a=this._optDefaults||{};for(const n of Object.keys(a))!(n in this._opts)&&n in s&&JSON.stringify(s[n])===JSON.stringify(a[n])&&delete s[n];if(this._invalidJson){this._showToast(this._tText("toast.invalid_json",{key:this._invalidJson},`"${this._invalidJson}" is not valid JSON - fix it or clear the field before saving.`),"error");return}const r=this._liveValidateSettings(t),i=new Set(Object.keys(r));if(i.size>0){const n={},l={};for(const[p,_]of Object.entries(s))i.has(p)?l[p]=_:n[p]=_;const c=this._changedOptions(n);this._pendingSettings={...this._pendingSettings,...l},Object.keys(c).length?await this._busyRun("save-settings",async()=>{try{await this._ws({type:`${w}/set_options`,entry_id:e.entry_id,options:c}),this._opts={...this._opts,...c},this._showToast(this._t("toast.saved_except_conflicts",{},"Saved. Fix the highlighted conflicts to save the rest."),"info")}catch(p){this._showToast(this._tText("msg.toast_save_failed",{error:p.message||p},"Save failed: "+(p.message||p)),"error")}}):this._showToast(this._t("toast.settings_conflicts",{},"Fix the highlighted setting conflicts before saving."),"error");return}const o=this._changedOptions(s);if(!Object.keys(o).length){this._pendingSettings={},this._dirtyOptKeys=new Set,this._showToast(this._t("toast.settings_no_changes",{},"No changes to save"),"info");return}await this._busyRun("save-settings",async()=>{try{const n=JSON.parse(JSON.stringify(this._preCascadeOpts||this._opts||{}));if(await this._ws({type:`${w}/set_options`,entry_id:e.entry_id,options:o}),this._opts={...this._opts,...o},this._prevOpts=n,this._cascadePending={},this._preCascadeOpts=null,this._pendingSettings={},this._dirtyOptKeys=new Set,this._stagedSuggestions){try{await this._ws({type:`${w}/clear_suggestions`,entry_id:e.entry_id})}catch{}this._stagedSuggestions=!1,this._suggestions=[]}this._showToast(this._t("toast.settings_saved",{},"Settings saved; integration reloading"))}catch(n){this._showToast(this._tText("msg.toast_save_failed",{error:n.message||n},"Save failed: "+(n.message||n)),"error")}})}}customElements.get("ha-washdata-panel")||customElements.define("ha-washdata-panel",Ze);