`;const e=!!t.is_user_paused,s=e?"user_paused":t.detector_state||"unknown",r=!!t.recording,n=r?"var(--error-color, #f44336)":this._stateColor(s),a=r?this._t("status.recording",{},"Recording"):this._stateLabel(s),o=r||Rt.includes(s),i=t.cycle_progress_pct,l=t.time_remaining_s,c=t.current_program,p=!!t.manual_program,_=c?null:t.armed_program||null,u=c||_||"auto_detect",g=(this._profiles||[]).map(E=>E.name);c&&!g.includes(c)&&g.unshift(c),_&&!g.includes(_)&&g.unshift(_);const k=g.map(E=>``).join(""),$=c?p?this._t("badge.manual",{},"(manually selected)"):this._t("badge.auto",{},"(auto-detected)"):_?this._t("badge.armed",{},"(applies to the next cycle)"):"",w=$?`${$}`:"",v=`
${ct(this._t("lbl.program_tip",{},"Override which profile is matched to the current cycle. Auto-detect lets the integration pick the best match automatically. Pin a specific program to force-match it when auto-detect is wrong or you know what is running. Pick one before starting the appliance and it is applied as soon as the next cycle begins."))}
${w}
`,T=[];t.recording&&this._canEdit()&&T.push(`
\u25CF
${this._t("msg.recording_in_progress",{},"Recording in progress")}
Live Match Debug ${ct("Confidence: how closely the current power curve matches the top candidate profile (0-100%). Ambiguous: the two best candidates score within 5% of each other - the label is uncertain until the cycle finishes.")}
${this._t("hdr.manual_recording",{},"Manual Recording")}${ct(this._t("hdr.manual_recording_tip",{},"Run a cycle intentionally while WashData records the power trace. Start just before the appliance starts, Stop when it finishes, then Process to save it as a named profile."))}${o?`${o}`:""}
${i}
`}_htmlHistory(){const t=this._cycles||[],e=this._refCycles||[],s=e.concat(t),r=this._canEdit(),n=this._selectMode&&r,a=this._cycleSel,{col:o,dir:i}=this._cycleSort,{text:l,status:c}=this._cycleFilter,p=this._mlById||{},_=new Set((this._feedbacks||[]).map(b=>b.cycle_id)),u=b=>p[b.id],g=b=>{const E=u(b);return!!(E&&E.ml_review&&E.ml_review.reviewed_at)},k=b=>{const E=u(b);return!!(E&&E.ml_review&&E.ml_review.golden)},$=b=>{if(_.has(b.id))return!0;if(g(b))return!1;const E=u(b),W=E&&E.ml_quality_label;return["uncertain","review"].includes(W)||["force_stopped","interrupted"].includes(b.status)},x=s.filter($).length;let w=s;if(l){const b=l.toLowerCase();w=w.filter(E=>(E.profile_name||E.matched_profile||"").toLowerCase().includes(b))}c==="unlabelled"?w=w.filter(b=>!b.profile_name&&!b.matched_profile):c==="needs_review"?w=w.filter($):c==="imported"?w=w.filter(b=>b.is_reference):c&&(w=w.filter(b=>(b.status||"completed")===c));const v={date:b=>b.start_time?new Date(b.start_time).getTime():0,confidence:b=>b.match_confidence,duration:b=>b.duration,energy:b=>b.energy_kwh!=null?b.energy_kwh:b.energy_wh!=null?b.energy_wh/1e3:null,cost:b=>b.cost!=null?b.cost:-1,status:b=>b.status||"completed",profile:b=>(b.profile_name||b.matched_profile||"\uFFFF").toLowerCase()};w=ae(w,v[o]||v.date,i);const T=b=>b==="completed"?"var(--success-color, #4caf50)":b==="interrupted"?"var(--error-color, #f44336)":b==="force_stopped"?"var(--warning-color, #ff9800)":"var(--secondary-text-color)",I=b=>{if(!b.is_reference)return"";const E=b.cycle_origin==="backfill",W=E?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` ${E?"\u{1F557}":"\u{1F4E5}"}`},D=b=>k(b)?' \u2B50':_.has(b.id)?' \u{1F4AC}':g(b)?' \u2713':$(b)?' \u25CF':"",F=b=>{if(b.anomaly!=="overrun")return"";const E=b.overrun_ratio?" "+this._t("badge.overrun_ratio",{x:Number(b.overrun_ratio).toFixed(1)},`(${Number(b.overrun_ratio).toFixed(1)}x expected)`):"";return` \u23F1`},B=b=>{if(b.anomaly!=="underrun")return"";const E=b.underrun_ratio?" "+this._t("badge.underrun_ratio",{pct:Math.round(b.underrun_ratio*100)},`(${Math.round(b.underrun_ratio*100)}% of expected)`):"";return` \u26A1`},R=b=>{if(!b.energy_anomaly||b.energy_anomaly==="none")return"";const E=b.energy_anomaly==="energy_spike",W=b.energy_z_score!=null?` (${b.energy_z_score>0?"+":""}${Number(b.energy_z_score).toFixed(1)}\u03C3)`:"",N=E?"badge.energy_spike":"badge.energy_low",H=E?"Higher energy than usual":"Lower energy than usual",j=E?"\u{1F53A}":"\u{1F53B}",q=E?"var(--error-color,#f44336)":"var(--info-color,#2196f3)";return` ${j}`},P=b=>{const E=Array.isArray(b.artifacts)?b.artifacts.length:0;return E?` 1?"ies":"y"} detected (e.g. door opened mid-cycle) \u2014 open to see them on the graph`))}" style="color:var(--warning-color,#ff9800)">\u26A0`:""},A=b=>{const E=Array.isArray(b.restart_gaps)?b.restart_gaps.length:0;return E?` 1?"s":""} during this cycle \u2014 power trace has a hole`))}" style="color:var(--info-color,#2196f3)">\u21BB`:""},M=this._hass&&this._hass.config&&this._hass.config.currency||"",O=b=>{if(b.cost==null)return"-";const E=`${b.cost.toFixed(2)}${M?" "+M:""}`;if(b.energy_price_mode==="dynamic"&&b.energy_price!=null){const W=this._t("col.cost_dynamic_tip",{price:b.energy_price.toFixed(4),cur:M},`Time-weighted: charged at the price in force during the cycle (effective ${b.energy_price.toFixed(4)} ${M}/kWh).`);return`${E}`}return E},G=w.map(b=>{const E=b.profile_name||b.matched_profile,W=b.match_confidence!=null?b.match_confidence*100:null,N=b.status||"completed",H=b.energy_kwh!=null?b.energy_kwh:b.energy_wh!=null?b.energy_wh/1e3:null,j=n,q=j?``:``,L={completed:this._t("status.completed",{},"Completed"),interrupted:this._t("status.interrupted",{},"Interrupted"),force_stopped:this._t("status.force_stopped",{},"Force stopped"),active:this._t("status.active",{},"Active")}[N]||N,V=`${I(b)}${D(b)}${F(b)}${B(b)}${R(b)}${P(b)}${A(b)}`.trim();return`
${vt(this._t("lbl.profile",{},"Profile"),"profile",o==="profile",i,"cycsort","",this._t("col.profile_tip",{},"Matched program name. Unlabelled means no profile matched at end of cycle."))}
${this._t("lbl.flags",{},"Flags")}
${vt(this._t("lbl.status",{},"Status"),"status",o==="status",i,"cycsort","",this._t("col.status_tip",{},"Cycle outcome: Completed (natural end), Interrupted (abrupt power drop), Force Stopped (manual), or Needs Review (feedback pending)."))}
${vt(this._t("lbl.date",{},"Date"),"date",o==="date",i,"cycsort","",this._t("col.date_tip",{},"Date and time the cycle started."))}
${vt(this._t("lbl.duration",{},"Duration"),"duration",o==="duration",i,"cycsort","right",this._t("col.duration_tip",{},"Total cycle run time from start to end."))}
${vt(this._t("lbl.energy",{},"Energy"),"energy",o==="energy",i,"cycsort","right",this._t("col.energy_tip",{},"Total energy consumed (kWh). Computed by integrating power over time."))}
${vt(this._t("lbl.cost",{},"Cost"),"cost",o==="cost",i,"cycsort","right",this._t("col.cost_tip",{},"Energy cost for this cycle, frozen at completion. With Time-Weighted Cost on, the power trace is charged at the price in force at each moment; otherwise energy x the single price in effect at the end. Set a price under Settings to populate it."))}
${vt(this._t("lbl.confidence",{},"Confidence"),"confidence",o==="confidence",i,"cycsort","right",this._t("col.confidence_tip",{},"Profile match confidence (0-100%). How closely the cycle power curve matched the identified program."))}
${s.length?this._t("msg.no_cycles_match",{},"No cycles match the current filter."):this._t("msg.no_cycles_yet",{},"No cycles recorded yet.")}
`:`
${et}${G}
`}
${m}
`;return(this._cyclesError?`
${this._t("msg.fetch_error",{},"Failed to load data.")}
`:"")+f}_trendIcon(t){return t==="up"?`\u2191`:t==="down"?`\u2193`:""}_profileCardHtml(t){const e=t.avg_duration?this._t("lbl.duration_avg",{v:Math.round(t.avg_duration/60)},`~${Math.round(t.avg_duration/60)}m avg`):this._t("lbl.no_duration",{},"no duration"),s=t.avg_energy!=null?` \xB7 ${yt(t.avg_energy)}/cycle`:"",r=t.avg_energy!=null&&t.cycle_count?` \xB7 ${yt(t.avg_energy*t.cycle_count)} total`:"",n=this._hass&&this._hass.config&&this._hass.config.currency||"",a=t.avg_cost!=null?` \xB7 ${this._t("lbl.avg_cost",{},"Avg")} ${t.avg_cost.toFixed(2)}${n?" "+n:""}/${this._t("lbl.per_cycle_short",{},"cycle")}`:"",o=(this._profileHealth||{})[t.name],i=(this._profileTrends||{})[t.name];let l="";o&&o.health_status==="poor"?l=`\u26A0 ${this._t("badge.poor_fit",{},"poor fit")}`:o&&o.health_status==="fair"&&(l=`${this._t("badge.fair_fit",{},"fair fit")}`);let c="";if(i){const D=this._trendIcon(i.duration_trend),F=i.energy_trend?this._trendIcon(i.energy_trend):"";if(i.duration_trend!=="stable"||i.energy_trend==="up"){const B=[];if(i.duration_trend!=="stable"){const P=`${i.duration_slope_pct>0?"+":""}${i.duration_slope_pct}`;B.push(i.duration_trend==="up"?this._t("msg.duration_trend_up_tip",{pct:P},`Duration up (${P}%/cycle)`):this._t("msg.duration_trend_down_tip",{pct:P},`Duration down (${P}%/cycle)`))}if(i.energy_trend&&i.energy_trend!=="stable"){const P=`${i.energy_slope_pct>0?"+":""}${i.energy_slope_pct}`;B.push(i.energy_trend==="up"?this._t("msg.energy_trend_up_tip",{pct:P},`Energy up (${P}%/cycle)`):this._t("msg.energy_trend_down_tip",{pct:P},`Energy down (${P}%/cycle)`))}const R=B.join(", ")||this._t("msg.performance_trending",{},"Performance trending");c=`${D}${F||""}`}}const p=this._constants&&this._constants.PROFILE_MIN_WARMUP_CYCLES||5,_=o&&o.cycle_count||0,g=_
${this._t("msg.warmup_badge",{done:_,needed:p},`Still learning (${_}/${p} cycles)`)}`:"",k=t.is_imported?`\u{1F4E5} ${this._t("status.imported",{},"Imported")}`:"",$=(this._profileAdvisories||[]).find(D=>D&&D.profile===t.name&&D.code==="unmatchable"),x=$?`\u26A0 ${this._t("badge.unmatchable",{},"can't be matched")}`:"",w=(this._profileTerminal||{})[t.name];let v="";if(w&&w.quiet_before_s>0&&w.seen_in>=2){const D=Math.max(1,Math.round(w.quiet_before_s/60)),F=Number(w.event_watts||0).toFixed(0),B=Math.round(w.event_seconds||0),R=this._t("badge.quiet_tail_tip",{mins:D,secs:B,watts:F,seen:w.seen_in,measured:w.measured},`Near the end this program goes quiet for about ${D} min, then draws about ${F} W for ${B} s before finishing. Seen in ${w.seen_in} of ${w.measured} measured cycles: the appliance does not do it every run.`);v=`${this._t("badge.quiet_tail",{mins:D},`~${D}m quiet tail`)}`}const T=[x,l,c,v,g,k].filter(Boolean).join(" "),I=Array.isArray(t.signature_curve)&&t.signature_curve.length>=3?``:"";return`
`}_paintSparkline(t,e){if(!t||!Array.isArray(e)||e.length<3)return;const s=(getComputedStyle(this).getPropertyValue("--primary-color")||"#03a9f4").trim()||"#03a9f4",r=window.devicePixelRatio||1,n=t.getBoundingClientRect(),a=t.width=Math.max(1,Math.round((n.width||64)*r)),o=t.height=Math.max(1,Math.round((n.height||20)*r)),i=t.getContext("2d");i.clearRect(0,0,a,o);const l=Math.max(...e,1),c=2*r,p=u=>c+(e.length===1?0:u/(e.length-1)*(a-2*c)),_=u=>o-c-Math.max(0,u)/l*(o-2*c);i.beginPath(),e.forEach((u,g)=>{const k=p(g),$=_(u);g===0?i.moveTo(k,$):i.lineTo(k,$)}),i.strokeStyle=s,i.lineWidth=1.5*r,i.lineJoin="round",i.lineCap="round",i.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 r of this._profiles||[])s[r.name]=r;e.forEach(r=>{const n=s[r.dataset.sparkProf];this._paintSparkline(r,n&&n.signature_curve||[])})}_htmlProfiles(){const t=this._busy.has("rebuild-envelopes"),e=this._canEdit(),s={};this._profiles.forEach(g=>{s[g.name]=g});const r=this._profileGroups||{groups:[],suggestions:[]},n=new Set;r.groups.forEach(g=>(g.members||[]).forEach(k=>n.add(k)));const a=r.groups.map(g=>{const k=(g.members||[]).map(T=>s[T]?this._profileCardHtml(s[T]):"").join(""),$=Math.round((g.cohesion!=null?g.cohesion:1)*100),x=g.cohesive?`${this._t("lbl.cohesion_good",{pct:$},"cohesion "+$+"%")}`:`${this._t("lbl.cohesion_low",{pct:$},"\u26A0 low cohesion "+$+"%")}`,w=g.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.")}
\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("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?`
`:""}
${a}
${this._profiles.length===0?`
\u{1F4CA}
${this._t("msg.no_profiles_yet",{},"No profiles yet. Create one from a labelled cycle.")}
${this._t("msg.group_modal_help",{},"Group programs with the same shape that differ in temperature/spin (durations may vary). Matching scores the group as one candidate, then picks the best-fitting member. Pick at least 2; the overlay shows how alike they are.")}
\u{1F4A1} ${this._t("msg.showing_suggestions",{count:F},`Showing ${F} setting${F>1?"s":""} with suggestions.`)} ${this._t("msg.show_all_settings",{},"Show all settings")}.
${R}
`:`
\u{1F4A1} ${this._t("msg.tuning_suggestions_available",{count:F},`${F} tuning suggestion${F>1?"s":""} available from observed cycles. They appear beside the relevant fields.`)}
${R}
${P}
`}_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 r=(s.profile_name||"").trim();r&&(t.has(r)||t.set(r,[]),t.get(r).push(s))}const e=Array.from(t.entries()).map(([s,r])=>({program:s,cycles:r})).sort((s,r)=>s.program.localeCompare(r.program));for(const s of this._shareAllPrograms||[])t.has(s)||e.push({program:s,cycles:[],noCycles:!0});return e}_catalogEntryKey(){const t=this._opts||{};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 r=e==="brand"?s.brand:s.device;if(!r)return null;const n=e==="brand"?r.brand:r.model;return String(n||"").toLowerCase()===String(t||"").trim().toLowerCase()?r:null}async _loadCatalogEntry(t){const e=this._devices[this._selIdx],s=this._opts||{},r=(s.store_brand||"").trim(),n=(s.store_model||"").trim();if(!e||!this._onlineEnabled()||!r||!n){this._catalogEntry&&this._catalogEntry.key===t&&(this._catalogEntry.loading=!1);return}let a=null;try{a=await this._ws({type:`${y}/store_get_catalog_entry`,entry_id:e.entry_id,brand:r,model:n,appliance_type:s.device_type||""})}catch{}!this._catalogEntry||this._catalogEntry.key!==t||(this._catalogEntry={key:t,loading:!1,brand:a&&a.brand||null,device:a&&a.device||null,deviceId:a&&a.device_id||null},this._isActiveEntry(e.entry_id)&&this._renderPreservingFormEdits())}_refreshComboAfterLoad(t,e,s=!0){const r=this.shadowRoot;for(const n of Array.isArray(t)?t:[t]){const a=r&&r.getElementById(n);if(a&&r.activeElement===a){if(s){a._wdSyntheticInput=!0;try{a.dispatchEvent(new Event("input",{bubbles:!0}))}finally{a._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 r=String((this._opts||{}).store_brand||"").trim();if(!r)return;(this._catalog.forBrand!==r||this._catalog.devices===void 0)&&(this._catalog.forBrand=r,this._catalog.devices=null,this._loadCatalogDevices(r))}}}_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(r=>e.startsWith(r))||(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,r)=>String(s.brand||"").localeCompare(String(r.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 r=await this._ws({type:`${y}/store_list_brands`,entry_id:s.entry_id,query:t||null,include_pending:!this._catalog.approvedOnly}),n=r&&r.items||[];t?(this._mergeBrandCandidates(n),(this._catalog.brandPrefixes||(this._catalog.brandPrefixes=[])).push(t)):(this._catalog.brands=n,this._catalog.brandsFull=!0)}catch{Array.isArray(this._catalog.brands)||(this._catalog.brands=[]),e=!0}this._entityListCache.store_brand=(this._catalog.brands||[]).map(r=>r.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 r of this._catalog.typeDevices||[]){const n=String(r&&r.model||"").trim();if(!n)continue;const a=r.brand?`${n} \xB7 ${r.brand}`:n;t.has(a)||(t.set(a,r),e.push(a))}this._catalog.modelIndex=t;const s=(this._catalog.brands||[]).map(r=>r.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:`${y}/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:`${y}/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(),r=(this._opts.store_model||"").trim();if(!s||!r){e.profiles=[],this._modal===e&&this._render();return}try{const n=await this._ws({type:`${y}/store_get_device_profiles`,entry_id:t.entry_id,brand:s,model:r,appliance_type:this._opts.device_type||""});if(this._modal!==e)return;e.profiles=n&&n.items||[],e.deviceId=n&&n.device_id||null}catch{this._modal===e&&(e.profiles=[])}this._modal===e&&this._render()}_htmlAutomations(){const t=this._deviceAutomations||[],e=t.length?t.map(n=>`\u{1F517} ${d(n.name)}${n.enabled?"":' (off)'}`).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:[],r=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.")}
`:"";return`
${this._t("hdr.automations",{},"Automations")}
${this._t("msg.automations_intro",{start:"ha_washdata_cycle_started",end:"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.")}
`}async _loadDeviceAutomations(t){this._deviceAutomations=[];const e=this._hass;if(!(!e||!e.callWS))try{let s=null;const r=e.devices||{};for(const i of Object.values(r))if((i.config_entries||[]).includes(t)){s=i.id;break}const n=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 a=n&&n.automation||[],o=e.states||{};this._deviceAutomations=a.map(i=>{const l=o[i]&&o[i].attributes||{};return{entity_id:i,id:l.id,name:l.friendly_name||i,enabled:o[i]?o[i].state==="on":!0}}).filter(i=>i.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,r=t==="started"?"ha_washdata_cycle_started":"ha_washdata_cycle_ended",n=t==="started"?"started":"finished",a={alias:`${e.title||"WashData"}: cycle ${n}`,description:`Runs when the WashData ${e.title||""} cycle ${n}. Add your actions (notify, lights, ...).`,mode:"single",trigger:[{platform:"event",event_type:r,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,a),this._navigate("/config/automation/edit/"+o)):this._navigate("/config/automation/edit/new")}catch(i){this._showToast(this._t("msg.toast_automation_failed",{error:i.message||i},"Could not create automation: "+(i.message||i)),"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 r={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},n="washdata_"+Date.now().toString(36);if(!e||!e.callApi){this._showToast(this._t("msg.toast_no_automation",{},"Cannot create automation here"),"error");return}let a=!1;try{await e.callApi("POST","config/automation/config/"+n,r),a=!0,await this._ws({type:`${y}/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/"+n)}catch(o){const i=o.message||o;if(!a){this._showToast(this._t("msg.toast_convert_failed",{error:i},"Convert failed: "+i),"error");return}let l=null;try{const c=await this._ws({type:`${y}/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/"+n);return}if(l===!0){try{await e.callApi("DELETE","config/automation/config/"+n),this._showToast(this._t("msg.toast_convert_rolled_back",{error:i},"Migration failed and was rolled back (no automation left behind): "+i),"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",r=l=>!(l.id==="ml_training"||e&&l.notDeviceTypes&&l.notDeviceTypes.includes(e)||e&&l.onlyDeviceTypes&&!l.onlyDeviceTypes.includes(e)||s&&!this._secHasBasicFields(l)),n=xt.find(l=>l.id===this._settingsSec&&r(l))||xt.find(l=>r(l))||xt[0],a=n.intro||this._t("section."+n.id+".intro",{},"")?`
${this._t("msg.notify_services_hint",{entity:"notify.<name>",vars:""+d(Et)+""},"Use {entity} service IDs (comma-separated for multiple). Template variables: {vars}.")}
${this._t("msg.no_settings_match",{q:e},`No settings match "${d(e)}"`)}
`}_mlSugKeys(t){const e=t||Object.assign({},this._optDefaults,this._opts,this._pendingSettings||{});return this._mlSugKeysFrom(this._mlSettings,e,this._lockedSuggestions)}_mlSugKeysFrom(t,e,s){const r=new Set(s||[]),n=e||{},a=new Set;for(const[o,i]of Object.entries(t||{}))r.has(o)||i&&i.ml_value!=null&&!Vt(i.ml_value,n[o])&&a.add(o);return a}_sugCountsForDevice(t){if(!t)return{classic:0,ml:0,total:0};const e=this._devices[this._selIdx],s=!!(e&&e.entry_id===t.entry_id),r=Array.isArray(t.suggestion_keys)?t.suggestion_keys:null,n=r?r.length:t.suggestions_count||0,a=s?Object.assign({},t.option_defaults||{},t.options||{},this._opts,this._pendingSettings||{}):Object.assign({},t.option_defaults||{},t.options||{}),o=this._mlSugKeysFrom(s?this._mlSettings:this._mlSettingsByEntry[t.entry_id],a,s?this._lockedSuggestions:this._lockedByEntry[t.entry_id]),i=r?[...o].filter(l=>!r.includes(l)).length:o.size;return{classic:n,ml:i,total:n+i}}_htmlSettingsSugOnly(t){const e=new Set((this._suggestions||[]).map(a=>a.key));for(const a of this._mlSugKeys(t))e.add(a);if(!e.size)return`
${this._t("msg.no_suggestions",{},"No active suggestions.")}
`;const s=this._opts&&this._opts.device_type||"",r=xt.filter(a=>!(a.id==="ml_training"||s&&a.notDeviceTypes&&a.notDeviceTypes.includes(s)||s&&a.onlyDeviceTypes&&!a.onlyDeviceTypes.includes(s)));let n="";for(const a of r){const i=(a.fields||(a.groups||[]).flatMap(c=>c.fields||[])).filter(c=>e.has(c.key));if(!i.length)continue;const l=i.map(c=>this._renderField(c,t)).filter(Boolean).join("");l&&(n+=`
${this._t("msg.ml_intro",{},"WashData ships with smart models that work out of the box.")}
${this._htmlMlStatusSection(e,r)}
${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.")}
${this._t("msg.saving_triggers_reload",{},"Saving triggers an integration reload.")}
`;const n=Object.keys(t.on_device_models||{}).length,a=n?`${this._t("ml.personalized",{},"\u25CF Personalized to this machine")}${this._t("lbl.models_fine_tuned",{count:n},"(fine-tuned models: "+n+")")}`:`${this._t("ml.builtin_models",{},"\u25CF Using built-in models")}`,o=t.cycle_count||0,i=t.min_cycles||0,l=o>=i,c=i>0?Math.min(100,Math.round(o/i*100)):100,p=l?"var(--success-color,#4caf50)":"var(--warning-color,#ff9800)",_=Math.max(0,i-o),u=l?this._t("msg.enough_data",{current:o,min:i},`Enough data to learn from (${o}/${i} cycles).`):this._t("msg.collecting_data",{need:_,current:o,min:i},`Collecting data. Cycles still needed before fine-tuning can start: ${_} (${o}/${i}).`),g=`
`,k=t.last_trained?rt(t.last_trained):"never",$=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.ml_learned",{},"What WashData has learned")}
${this._t("msg.ml_learned_intro",{},"Models fine-tuned to this machine.")}
${n}
`}_mlQualityChip(t){let e=0,s="",r=t.metric_key?this._t(t.metric_key,t.metric_params||{},t.metric||""):t.metric||"";if(t.auc!=null)e=Math.max(0,Math.min(1,(t.auc-.5)/.5))*100,s=t.auc>=.85?this._t("ml.fit_strong",{},"Strong"):t.auc>=.75?this._t("ml.fit_good",{},"Good"):t.auc>=.65?this._t("ml.fit_fair",{},"Fair"):this._t("ml.fit_weak",{},"Weak");else if(t.model_mae!=null&&t.naive_mae!=null&&t.naive_mae>0){const a=Math.max(0,(t.naive_mae-t.model_mae)/t.naive_mae);e=Math.min(1,a)*100,s=a>=.5?this._t("ml.fit_strong",{},"Strong"):a>=.2?this._t("ml.fit_good",{},"Good"):this._t("ml.fit_slight",{},"Slight"),r=this._t("ml.better_than_baseline",{pct:(a*100).toFixed(0),metric:r},`${(a*100).toFixed(0)}% better than the baseline estimate (${r})`)}else return"";const n=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 \u2014 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]}`:""}_htmlMatchingTuningCard(){const t=this._mlTrainingStatus,e=t&&t.matching;if(!e)return"";const s=e.defaults||{},r=e.tuned||null,n=r&&r.config||null,a=e.active==="tuned"&&n,o=this._busy.has("ml-revert-match"),i=u=>u==null||isNaN(u)?"-":Number(u).toFixed(2),l=[["corr_weight","Shape (correlation)"],["duration_weight","Duration agreement"],["energy_weight","Energy agreement"],["dtw_ensemble_w","DTW derivative blend (DDTW)"]].map(([u,g])=>{const k=s[u],$=a?n[u]:s[u],x=a&&k!=null&&$!=null&&Math.abs(k-$)>1e-9;return`
${this._t("msg.matching_tuning_intro",{},"When learning, WashData also adjusts how much program matching weighs shape versus duration and energy.")}
${c}
${this._t("lbl.emphasis",{},"Emphasis")}
${this._t("lbl.default",{},"Default")}
${this._t("lbl.in_use",{},"In use")}
${l}
${p}
`}_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"],["end_repeat_count","End Repeat Count","","Low readings in a row before ending","advanced"],["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","Power must fall below this between pulses for anti-wrinkle to stay active","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 matched program's expected duration a cycle must reach before Smart Termination may end it early; lower it for load- or temperature-dependent machines","advanced"],["anti_crease_finalize_ratio","Anti-Crease Finalize Ratio","","Fraction of the matched program's expected duration a cycle must reach before the anti-crease tumble tail may be finalised. Lower it on a dryer whose sensor-dry runtime follows the load, so its tail is recognised instead of sitting until the fallback timeout. Leave washing machines at the default: there this fraction is what keeps a quiet mid-wash phase from being mistaken for the tail. Separate from the Smart Termination Ratio, which gates a different check.","advanced"],["curve_preroll_seconds","Curve Pre-roll","s","Seconds of readings from aborted start attempts that may be carried into the front of a cycle's curve. Machines that probe before settling (programme selection, door lock, first fill) can drop the first minutes of real activity from every curve. 0 turns this off. Note that enabling it moves the recorded start earlier, so cycles recorded before and after the change carry different durations for the same program until the older ones age out - expect the learned averages to drift for a while.","timing"],["profile_match_min_duration_ratio","Min Duration Ratio","","Stage 1: shortest run (vs the profile) still allowed to match","matching"],["profile_match_max_duration_ratio","Max Duration Ratio","","Stage 1: longest run (vs the profile) still allowed to match","matching"],["corr_weight","Correlation Weight","","Stage 2: balance between curve shape (correlation) and power level (MAE); default 0.45","matching"],["keep_min_score","Keep Min Score","","Stage 2: floor score to stay in the race; default 0.1 admits weak matches, later stages pick the best","matching"],["dtw_bandwidth","DTW Bandwidth","","Stage 3: Sakoe-Chiba warp band (0 = DTW off; default 0.2 = 20% of cycle length)","matching"],["dtw_blend","DTW Blend","","Stage 3: 0 = core score only, 1 = DTW score only, 0.5 = equal blend (default)","matching"],["dtw_ensemble_w","DTW Ensemble Weight","","Stage 3 (ensemble mode): weight on scaled-L1 vs derivative DTW; default 0.7 favours level-aware","matching"],["dtw_ddtw_scale","DDTW Scale","","Stage 3: DDTW half-saturation distance; smaller = more shape-sensitive (default 30)","matching"],["dtw_refine_top_n","DTW Refine Top-N","","Stage 3: candidates DTW re-scores; raise to 7-9 if correct profile ranks 4th-5th (default 5)","matching"],["duration_weight","Duration Weight","","Stage 4: how strongly run-length agreement affects the final score (default 0.22)","matching"],["energy_weight","Energy Weight","","Stage 4: how strongly energy agreement affects the final score (default 0.22)","matching"],["duration_scale","Duration Scale","","Stage 4: log-ratio where duration agreement halves; smaller = stricter penalty (default 0.175)","matching"],["energy_scale","Energy Scale","","Stage 4: log-ratio where energy agreement halves; smaller = stricter penalty (default 0.25)","matching"]]}_pgFieldVal(t,e){const s=e||{};if(s[t]!==void 0)return s[t];const r=this._pgEffective||{};if(r[t]!==void 0&&r[t]!==null)return r[t];const n=this._opts||{};if(n[t]!==void 0&&n[t]!==null)return n[t];const a=Ct[t]||{};if(a.def!==void 0)return a.def;const o=this._constants&&this._constants.pgMatchDefaults||{};return o[t]!==void 0?o[t]:Yt[t]!==void 0?Yt[t]:""}async _pgFetchSettings(t,e=!0){try{const s=await this._ws({type:`${y}/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(r=>r.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:{},this._pgSuggMl=t.ml_suggestions&&typeof t.ml_suggestions=="object"?t.ml_suggestions:null,this._pgMlSuggEnabled=!!t.ml_suggestions_enabled}async _pgFetchSuggestions(t){try{const e=await this._ws({type:`${y}/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 r=this._pgStagedVal(s);if(r==null)continue;const n=t[s];n!=null&&this._pgSameVal(r,n)||e.push(s)}return e}_pgSameVal(t,e){if(typeof t=="boolean"||typeof e=="boolean")return!!t==!!e;const s=parseFloat(t),r=parseFloat(e);return!isNaN(s)&&!isNaN(r)?Math.abs(s-r)<1e-9:String(t)===String(e)}_pgIsPublishable(t){return Array.isArray(this._pgPublishable)?this._pgPublishable.includes(t):!!Ct[t]}_htmlPgControlPanel(){const t=this._pgChangedKeys(),e=this._canEdit(),s=!!this._pgEffective,r=t.filter($=>this._pgIsPublishable($)),n=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 \u2014 showing defaults.")}`,a=``+(this._pgPresets||[]).map($=>``).join(""),o=!!this._pgPresetSel,i=this._pgPresetLimit>0&&(this._pgPresets||[]).length>=this._pgPresetLimit&&!(this._pgPresets||[]).some($=>$.name===(this._pgPresetName||"").trim()),l=`
${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=`${i}
${l}
`,p=this._htmlPgStrip(),_=this._pgNeedsRestart?`
\u26A0 ${this._t("msg.pg_restart_note",{},"Restart Home Assistant to enable simulation tools.")}
`:"",u=`${o}${c}${p}
${this._htmlPgParamRows()}
${this._htmlPgAlerts()}${this._htmlPgAnalysis()}
`;return`
${this._t("hdr.playground",{},"Playground")}
${this._t("msg.playground_intro",{},"Explore how settings affect detection on your real cycle data. Nothing here changes live configuration until you explicitly apply it.")}
${d(this._t("lbl.pg_stress_toggle_desc",{},"Simulates the appliance staying at its standby draw after recording ends \u2014 shows if and when WashData stops the cycle."))}
`}_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"),stress_terminated:this._t("lbl.pg_alert_stress_ok",{},"Idle termination: cycle stopped"),stress_above_threshold:this._t("lbl.pg_alert_stress_warn",{},"Idle draw above stop threshold"),stress_hit_cap:this._t("lbl.pg_alert_stress_cap",{},"Hit safety cap")}[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,r=`
${this._t("lbl.last",{},"Last")}${this._t("lbl.cycles_lc",{},"cycles")}
${s?`\u2699 ${this._t("msg.pg_override_active",{},"Using your edited settings vs. current")}`:""}
`,n=t?this._htmlPgBatchBar():"",a=`
${this._t("msg.pg_history_intro2",{},"Replay your recent cycles through the real detector and matcher with the settings above. Click any row to load that cycle in the graph; edit a setting to see a before/after comparison.")}
${this._t("msg.process_history_hint",{},"Re-run matching on all stored cycles, refresh tuning suggestions, retrain the ML models (if enabled), recost cycles against your recorded energy prices, and recompute cycle health. Run this after a batch of reviews.")}
${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("hdr.import_power_history",{},"Import power history")}
${this._t("msg.import_history_description",{},"Already had a smart plug before WashData? Upload a history export of its power sensor, or read it straight from Home Assistant, and the normal detection runs over it so past cycles turn up in your Cycles list ready to name.")}
`:`
${this._t("msg.maintenance_requires_access",{},"Maintenance and export/import require full access.")}
${this._t("msg.odometer_correct_hint",{},"Set this if WashData recorded a run that never happened, or if the appliance had already run cycles before WashData was installed.")}
${i}${this._t("lbl.total_cycles_run",{},"cycles run in total")}
${this._t("msg.odometer_intro",{},"This total only ever rises. Deleting a cycle record does not change it, and it keeps counting past the stored-history limit, so service reminders stay correct.")}
${$||`
${this._t("msg.no_reminders_set",{},"No service reminders are set yet.")}
${this._t("msg.prefs_personal",{},"These apply to your Home Assistant account only.")}
${this._t("hdr.display",{},"Display")}
${Math.round((t.font_scale||1)*100)}%
${this._t("msg.font_size_hint",{},"Make everything in this panel larger or smaller. Applies to your account on this device.")}
${this._t("hdr.status_graph",{},"Status Graph")}
${Mt(`id="wd-pref-expected" ${t.show_expected!==!1?"checked":""}`,this._t("lbl.show_expected",{},"Show expected curve overlay (matched profile, orange)"))}
${Mt(`id="wd-pref-raw" ${t.show_raw?"checked":""}`,this._t("lbl.show_raw",{},"Show raw socket toggle in live power graph"))}
${Mt(`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)"))}
${Mt(`id="wd-rbac-enabled" ${t.enabled?"checked":""}`,this._t("lbl.enable_access_control",{},"Enable per-user access control"),"",this._t("msg.rbac_hint",{},"When off, every Home Assistant user has full access (the default). Administrators always have full access and can manage everyone."))}
${this._t("msg.store_declare_appliance",{},"Tell WashData which appliance you own and this tab shows the setups other people have shared for it. You can also type a brand above to look around.")}
`;const r=`
${this._t("store.no_results",{},"No matching appliances found. Try a different search.")}
${this._t("msg.store_sibling_hint",{},"Nothing shared for your exact model? A closely-related model from the same brand is usually a good starting point.")}
${this._t("msg.store_download_device_intro",{},"Adopt every shared program and its reference cycles onto your device. Your own recorded cycles and stats are not affected.")}
${Ut(`data-action="store-toggle-dl-settings" ${this._dlSettings?"checked":""} ${n?"disabled":""}`,this._t("lbl.adopt_settings",{},"Also adopt settings"),ct(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.")))}
${this._t("store.connected_as",{name:d(e.name||e.uid||"")},`Connected as ${d(e.name||e.uid||"")}`)}
`:`
${this._t("store.not_connected",{},"Not connected. Connect a GitHub account to confirm appliances and share your own cycles.")}
`:"";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, and confirm appliance entries. One connection applies to your whole WashData integration; appliance brand and model are set per device under Basic. All online features are opt-in and off by default.")}
${this._t("msg.refresh_catalog_hint",{},"The community brand and appliance lists are cached to keep the shared store within its daily budget. Refresh to pick up entries added or approved by others.")}
${this._t("msg.manual_duration_ref_hint",{},"Only used when no reference cycle is selected \u2014 a reference cycle sets the duration from its own length.")}
${this._t("msg.edit_builtin_phase",{},"This is a built-in phase. Saving creates a custom override \u2014 the original is preserved and can be restored by deleting the override.")}
${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.")}
${d(g.program)}${this._t("msg.share_profile_no_cycles",{},"No reference cycles \u2014 mark a cycle as \u2B50 in the Cycles tab to include this profile")}
${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=d((this._opts.store_brand||"").trim()),c=d((this._opts.store_model||"").trim()),p=``,_=`${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 completed normally -- no mid-cycle interruptions, door-open events, or power blips.")}
${this._t("msg.share_guideline_review",{},"Your upload starts as pending and appears publicly once enough community members confirm it.")}
`,u=o?``:"";return`
${this._t("modal.store_share_device",{},"Share this device")}
${this._t("msg.store_share_device_intro",{brand:l,model:c},`Upload ${l} ${c} with the reference cycles you select. Others with the same appliance can adopt your programs. Entries are reviewed before appearing publicly.`)}
${this._t("msg.export_select_intro",{},"Tick exactly what to include. Selecting profiles without their cycles still exports a matchable program (its learned shape travels along).")}
${this._t("msg.import_analyze_hint",{},"Load an exported file (or paste its JSON). WashData analyzes it and shows exactly what can be imported before anything changes.")}
${this._t("msg.device_type_mismatch_warn",{src:a,local:o},"This export is from a different appliance type ("+a+" vs "+o+"). Programs and cycles can still be imported as reference data, but device-specific settings and real-history import are disabled.")}
${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.merge_hint",{},"Imported items are added; nothing local is lost. Name clashes are resolved below.")}
`,p=r.real_history_allowed!==!1,_=`
${t.cycleDest==="real_history"?this._t("msg.dest_real_history_hint",{},"Imported cycles count as this device's own history and feed energy/usage stats. Use for moving one appliance to a new install."):this._t("msg.dest_reference_hint",{},"Imported cycles only improve program matching and never affect usage/energy statistics.")}
`}_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")}[t]||""}_htmlHistoryImportModal(t){const e=`
${this._t("modal.history_import",{},"Import power history")}
${this._t("msg.hist_input_hint",{},"Upload a CSV downloaded from the History panel (entity, state, last changed), or let WashData read the sensor's history directly. Detection then runs over it exactly as it does live, and you choose which of the cycles it finds to keep.")}
${this._t("lbl.hist_since",{},"Since")}
${this._t("msg.hist_recorder_hint",{},"Reads from the date you pick up to now. Home Assistant keeps detailed history for 10 days by default and only hourly averages after that, which are too coarse to detect cycles from - pick a date further back only if your recorder is set to keep more.")}
${this._t("msg.hist_scanning",{},"Replaying your history through the detector. This runs in the background - you can close this dialog and come back to it.")}
`}if(t.step==="done"){const x=t.done||{},w=[this._t("msg.hist_imported_count",{n:x.imported||0},`${x.imported||0} cycles imported.`),x.duplicates?this._t("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}
${w.map(v=>`
${d(v)}
`).join("")}
${this._t("msg.hist_next_step",{},"They are in your Cycles list, tagged as imported history. Open one and use Label to name the program it belongs to.")}
`}const r=t.result||{},n=r.segments||[],a=r.parse||{},o=this._busy.has("hist-apply"),i=t.accept||new Set,l=[];a.rows_total&&l.push(this._t("msg.hist_rows_read",{n:a.rows_total},`${a.rows_total} readings read`)),a.first&&a.last&&l.push(`${rt(a.first)} \u2013 ${rt(a.last)}`),a.breaks&&l.push(this._t("msg.hist_breaks",{n:a.breaks},`${a.breaks} gaps where the sensor was unavailable`)),a.rows_other_entity&&l.push(this._t("msg.hist_other_entity",{n:a.rows_other_entity},`${a.rows_other_entity} readings for other entities ignored`)),a.entity_substituted_from&&l.push(this._t("msg.hist_entity_substituted",{used:a.entity_id||"?",wanted:a.entity_substituted_from},`read ${a.entity_id||"?"} (this device is configured for ${a.entity_substituted_from})`));const c=r.skipped||[],p={};c.forEach(x=>{p[x.reason]=(p[x.reason]||0)+1});const _=Object.entries(p).map(([x,w])=>`${w} \xD7 ${this._histSkipReason(x)}`).join(", "),u=r.settings||{},g=u.min_power!=null?this._t("msg.hist_settings_used",{w:u.min_power,s:u.off_delay},`Detected using this device's current settings (minimum power ${u.min_power} W, off delay ${u.off_delay} s).`):"";if(!n.length)return`${e}
${this._t("msg.hist_none_found",{},"No cycles could be detected in that history.")}
${this._t("msg.hist_found",{n:n.length},`Found ${n.length} cycles. Untick anything that does not look like a real run - nothing is stored until you import.`)}
\u{1F4E5} ${a?this._t("msg.imported_history_readonly",{},"Detected in imported power history. It shapes program 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. Shown for reference and matching. It is not counted in your stats and cannot be edited.")}
${this._t("msg.unsure_detected_prefix",{},"WashData is unsure it detected")} ${d(v)}${w.confidence!=null?` (${this._t("lbl.confidence",{},"confidence").toLowerCase()} ${(w.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("msg.split_intro",{},"Click the graph to add or remove a split point, or auto-detect by idle gaps. Each resulting segment can get its own profile.")}
`}else if(t.mode==="review"){const A=l&&l.ml_review||{},M=this._busy.has("cyc-review-save"),O=(Z,ot)=>``,et=[["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(([Z,ot])=>``).join(""),st=A.reviewed_at?`${this._t("lbl.reviewed_on",{date:new Date(A.reviewed_at).toLocaleDateString()},`reviewed ${new Date(A.reviewed_at).toLocaleDateString()}`)}`:"",J=ct(this._t("msg.review_profile_tip",{},"The program this cycle is labelled as. If the auto-detected program was wrong, correct it here - labelling teaches matching for future cycles.")),tt=ct(this._t("msg.review_quality_tip",{},"How clean this cycle is. Good = a textbook example of this program; Bad = detected but noisy or atypical; Unusable = mis-detected (merged, truncated or spurious). Drives the health score and which cycles are allowed to train the model.")),wt=ct(this._t("msg.review_recorded_tip",{},'Mark this as a hand-picked reference cycle for its program - the same role as a manually recorded cycle. Reference cycles are always kept, seed the matching template, and are never dropped by cleanup. (This is the "golden"/recorded flag; both are the same thing.)')),U=ct(this._t("msg.review_tags_tip",{},"Optional flags describing what went wrong with this cycle, so training and cleanup can account for it.")),ht=ct(this._t("msg.review_notes_tip",{},"Free-text notes for your own reference. Not used by matching or training."));I=`
${T}
${this._t("msg.review_confirm_help",{},"Confirm whether this cycle was detected correctly. Your reviews train the model on your machine - the more cycles you confirm, the better matching and health scoring get. A quick Good/Bad is enough.")}
${st}
${this._t("lbl.compare_profiles",{},"Compare with profiles")}${ct(this._t("msg.compare_profiles_tip",{},"Overlay other profile envelopes on the chart above to see which one best fits this cycle."))}
${(this._profiles||[]).map(Z=>{const ot=(t.overlays||[]).includes(Z.name),pt=ot?``:"";return``}).join("")||`${this._t("msg.no_profiles_compare",{},"No profiles to compare.")}`}
\u26A0 ${this._t("msg.artifact_header",{n:F.length},`${F.length} anomal${F.length>1?"ies":"y"} detected during this cycle`)}
${A}
${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 B="";const R=t.mode==="view"||t.mode==="review"?e.restart_gaps||[]:[];if(R.length){const A=R.map(M=>{const O=Math.round((M.gap_seconds||0)/60),G=O>=1?`${O}m`:`${Math.round(M.gap_seconds||0)}s`,et=M.match_confidence!=null?` \xB7 ${this._t("lbl.pct_match_confidence",{pct:Math.round(M.match_confidence*100)},`${Math.round(M.match_confidence*100)}% match confidence`)}`:"",st=M.profile?` (${d(M.profile)})`:"";return`
${this._t("msg.samples_decimated",{shown:A,total:M},`Showing ${A} of ${M} samples (thinned for display; peaks kept). A wide gap here is thinning, not missing data.`)}
${_.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:v},`score ${v}%`)}${T}${I}
${_.health_status==="poor"?`${this._t("msg.profile_poor_health_detail",{},"Cycles assigned to this profile have inconsistent shapes or low confidence. Consider rebuilding the envelope or reviewing labelled cycles.")}`:""}
${this._t("msg.shape_drift_advisory",{},"\u26A0 Shape drifting")}${d($)}${this._t("msg.shape_drift_detail",{},"The power pattern for this profile has shifted over time \u2014 possible appliance wear or maintenance needed (e.g. descaling, filter cleaning).")}
${this._t("msg.cleanup_intro",{},"Every labelled cycle overlaid. Tick outliers, then unlabel them (kept in history, but no longer shaping this profile) or delete them outright.")}
${o.length?``:`
${this._t("msg.no_cycles_profile",{},"No cycles for this profile.")}
${this._t("msg.manual_duration_hint",{},"The profile's average/expected cycle length, used for time-remaining estimates. Edit to set it; leaving it unchanged keeps the current value.")}
${this._t("msg.compare_selected_cycles",{},"Selected cycles (solid) \u2014 show / hide")}
${n}
${this._t("msg.compare_overlay_profiles",{},"Overlay profiles (faint)")}${ct(this._t("msg.compare_overlay_tip",{},"Overlay learned profile envelopes to see which program each cycle resembles."))}
`).join(""),S._kbd=-1,S.hidden=!1,this._positionComboDrop(f,S)},N=H=>{if(H){if(b){const j=m.closest(".wd-pillbox");if(j&&!Array.from(j.querySelectorAll(".wd-pill")).some(q=>q.dataset.val===H)){const q=document.createElement("span");q.className="wd-pill",q.dataset.val=H,q.appendChild(document.createTextNode(H));const L=document.createElement("button");L.type="button",L.className="wd-pill-x",L.setAttribute("aria-label","Remove"),L.textContent="\xD7",L.addEventListener("click",()=>q.remove()),q.appendChild(L),j.insertBefore(q,m)}f.value=""}else f.value=H,f.dispatchEvent(new Event("change",{bubbles:!0}));S.hidden=!0}};f.addEventListener("focus",()=>W(f.value)),f.addEventListener("input",()=>W(f.value,!f._wdSyntheticInput)),f.addEventListener("blur",()=>setTimeout(()=>{S.hidden=!0},150)),f.addEventListener("keydown",H=>{if(S.hidden&&H.key!=="ArrowDown")return;const j=S.querySelectorAll(".wd-combo-item");let q=S._kbd||-1;if(H.key==="ArrowDown"){if(H.preventDefault(),S.hidden){W(f.value);return}q=Math.min(q+1,j.length-1)}else if(H.key==="ArrowUp")H.preventDefault(),q=Math.max(q-1,0);else if(H.key==="Enter"&&!S.hidden){H.preventDefault(),q>=0?N(j[q].dataset.val):b&&f.value.trim()&&N(f.value.trim());return}else if(H.key==="Escape"){S.hidden=!0;return}else return;S._kbd=q,j.forEach((L,V)=>L.classList.toggle("kbd",V===q)),j[q]?.scrollIntoView({block:"nearest"})}),S.addEventListener("mousedown",H=>{const j=H.target.closest(".wd-combo-item");j&&(H.preventDefault(),N(j.dataset.val))})});const D=t.getElementById("wd-store-q");if(D){const m=()=>{const f=String(D.value||"").trim(),S=(this._catalog.modelIndex||new Map).get(f);if(S){this._storeOpenModel(S);return}f.toLowerCase()!==String(this._storeQuery||"").trim().toLowerCase()&&this._storeSearch(f)};D.addEventListener("change",m),D.addEventListener("keydown",f=>{if(f.key!=="Enter")return;const S=D.parentElement&&D.parentElement.querySelector(".wd-combo-drop");S&&!S.hidden&&S._kbd>=0||(f.preventDefault(),m())}),D.addEventListener("input",()=>{D._wdSyntheticInput||(clearTimeout(this._storeQTimer),this._storeQTimer=setTimeout(()=>{const f=String(D.value||"").trim();if(!f)return;((this._entityListCache||{}).store_brand||[]).some(b=>String(b).toLowerCase()===f.toLowerCase())&&m()},450))})}t.querySelectorAll(".wd-timerlist").forEach(m=>{const f=m.dataset.opt,S=()=>{if(!Array.isArray(this._pendingSettings[f])){const j=Array.isArray(this._opts&&this._opts[f])?this._opts[f]:[];this._pendingSettings[f]=j.map(q=>({...q}))}return this._pendingSettings[f]},b=j=>({offset_minutes:parseFloat(j.querySelector('[data-field="offset_minutes"]').value)||0,message:(j.querySelector('[data-field="message"]').value||"").trim(),auto_pause:j.querySelector('[data-field="auto_pause"]').checked}),E=j=>{const q=parseInt(j.dataset.tidx,10);if(isNaN(q))return;const L=S();L[q]=b(j)},W=j=>{const q=parseInt(j.dataset.tidx,10);isNaN(q)||(this._pendingSettings[f]=S().filter((L,V)=>V!==q)),j.remove(),m.querySelectorAll(".wd-timer-row").forEach((L,V)=>{L.dataset.tidx=V})},N=j=>{const q=j.querySelector(".wd-timer-remove");q&&q.addEventListener("click",()=>W(j)),j.querySelector('[data-field="auto_pause"]')?.addEventListener("change",()=>E(j)),j.querySelectorAll('[data-field="offset_minutes"],[data-field="message"]').forEach(L=>L.addEventListener("input",()=>E(j)))};m.querySelectorAll(".wd-timer-row").forEach(j=>N(j));const H=m.querySelector(".wd-timer-add");H&&H.addEventListener("click",()=>{const j=m.querySelectorAll(".wd-timer-row").length;S().push({offset_minutes:0,message:"",auto_pause:!1});const q=document.createElement("div");q.className="wd-timer-row",q.dataset.tidx=j,q.innerHTML=``,N(q),m.insertBefore(q,H)})});const F=t.getElementById("wd-status-prog");F&&F.addEventListener("change",()=>{const m=this._devices[this._selIdx];if(!m)return;const f=F.value,S=!Rt.includes(m.detector_state||"");this._ws({type:`${y}/set_program`,entry_id:m.entry_id,program:f}).then(()=>(this._showToast(f==="auto_detect"?this._t("msg.toast_auto_detect_enabled",{},"Auto-detect enabled"):S?this._t("msg.toast_program_armed",{program:f},`Program armed for the next cycle: ${f}`):this._t("msg.toast_program_set",{program:f},`Program set: ${f}`)),this._fetchAll())).catch(b=>this._showToast(this._t("msg.toast_failed",{error:b.message||b},"Failed: "+(b.message||b)),"error"))}),t.querySelectorAll("[data-cid]").forEach(m=>m.addEventListener("click",f=>{if(f.target.tagName==="INPUT"||m.dataset.action)return;const S=m.dataset.cid;m.dataset.selmode==="1"?(this._cycleSel.has(S)?this._cycleSel.delete(S):this._cycleSel.add(S),this._render()):this._onAction({dataset:{action:"open-cycle",cid:S}})})),t.querySelectorAll(".wd-cyc-overlay").forEach(m=>m.addEventListener("change",()=>{const f=this._modal;if(!f||f.type!=="cycle-detail")return;this._snapshotCycleReviewForm(t);const S=new Set(f.overlays||[]);m.checked?S.add(m.value):S.delete(m.value),f.overlays=[...S];const b=this._devices[this._selIdx];m.checked&&b?this._ensureProfileEnvs(b.entry_id,[m.value]).then(()=>this._render()):this._render()})),t.querySelectorAll(".wd-compare-cyc").forEach(m=>m.addEventListener("change",()=>{const f=this._modal;if(!f||f.type!=="compare-cycles")return;const S=f.hidden instanceof Set?f.hidden:new Set(f.hidden||[]);m.checked?S.delete(m.value):S.add(m.value),f.hidden=S,this._render()})),t.querySelectorAll(".wd-compare-overlay").forEach(m=>m.addEventListener("change",()=>{const f=this._modal;if(!f||f.type!=="compare-cycles")return;const S=new Set(f.overlays||[]);m.checked?S.add(m.value):S.delete(m.value),f.overlays=[...S];const b=this._devices[this._selIdx];m.checked&&b?this._ensureProfileEnvs(b.entry_id,[m.value]).then(()=>this._render()):this._render()})),t.querySelectorAll(".wd-pg-mem").forEach(m=>m.addEventListener("change",()=>{const f=this._modal;if(!f||f.type!=="profile-group")return;const S=t.getElementById("wd-pg-name");S&&(f.name=S.value);const b=new Set(f.members||[]);m.checked?b.add(m.value):b.delete(m.value),f.members=[...b],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 f=m.closest("[data-cid]"),S=f&&f.dataset.cid;S&&(m.checked?this._cycleSel.add(S):this._cycleSel.delete(S),this._render())}));const R=t.getElementById("wd-merge-prof");R&&R.addEventListener("change",()=>{const m=t.getElementById("wd-merge-new");m&&(m.style.display=R.value==="__create_new__"?"":"none")}),t.querySelectorAll(".wd-log-filter").forEach(m=>{const f=m.dataset.logfilter,S={level:"_logLevel",device:"_logDevice",component:"_logComponent",search:"_logSearch"}[f];if(!S)return;const b=f==="search"?"input":"change";m.addEventListener(b,()=>{this[S]=m.value,this._refreshLogViews(),this._syncLogFilters(m)})});const P=t.querySelector(".wd-log-resize");P&&P.addEventListener("pointerdown",m=>{m.preventDefault(),P.setPointerCapture(m.pointerId),P.classList.add("dragging");const f=t.querySelector(".wd-log-drawer"),S=m.clientX,b=f.offsetWidth,E=W=>{const N=Math.max(280,Math.min(900,b+(S-W.clientX)));f.style.width=N+"px",this._logDrawerWidth=N};P.addEventListener("pointermove",E),P.addEventListener("pointerup",()=>{P.removeEventListener("pointermove",E),P.classList.remove("dragging");try{localStorage.setItem("wd-log-width",String(this._logDrawerWidth))}catch{}},{once:!0})});const A=t.getElementById("wd-hist-file");A&&A.addEventListener("change",()=>{const m=A.files&&A.files[0];if(!m)return;const f=this._modal,S=new FileReader;S.onload=()=>{const b=t.getElementById("wd-hist-csv"),E=String(S.result||"");b&&(b.value=E),f&&f.type==="history-import"&&(f.csvText=E)},S.onerror=()=>this._showToast(this._t("toast.file_read_failed",{},"Could not read that file"),"error"),S.readAsText(m)}),t.querySelectorAll("[data-hist-pick]").forEach(m=>m.addEventListener("change",()=>{const f=this._modal;if(!f||f.type!=="history-import")return;const S=parseInt(m.dataset.histPick,10);m.checked?f.accept.add(S):f.accept.delete(S),this._render(),requestAnimationFrame(()=>this._drawHistorySparklines())}));const M=t.getElementById("wd-import-file");M&&M.addEventListener("change",()=>{const m=M.files&&M.files[0];if(!m)return;const f=new FileReader;f.onload=()=>{const S=t.getElementById("wd-import-json");S&&(S.value=String(f.result||""))},f.readAsText(m)}),t.querySelectorAll("[data-stab]").forEach(m=>m.addEventListener("click",()=>{this._toolsSubtab=m.dataset.stab;const f=this._devices[this._selIdx];f&&(this._tabLoading=!0,this._render(),this._fetchToolsData(f.entry_id).then(()=>{this._tabLoading=!1,this._render()}))})),t.querySelectorAll("[data-proftab]").forEach(m=>m.addEventListener("click",async()=>{this._profSubtab=m.dataset.proftab;const f=this._devices[this._selIdx];f&&this._profSubtab==="phase-catalog"&&!this._phases.length&&(this._tabLoading=!0,this._render(),await this._fetchPhases(f.entry_id),this._tabLoading=!1),this._render()}));const O=t.getElementById("wd-settings-save");O&&O.addEventListener("click",()=>this._saveSettings());const G=t.getElementById("wd-ml-save");G&&G.addEventListener("click",()=>this._saveSettings());const et=t.getElementById("wd-settings-form"),st=m=>{const f=m.target&&m.target.closest?m.target.closest("[data-opt]"):null;f&&f.dataset.opt&&this._dirtyOptKeys.add(f.dataset.opt)};et&&(et.addEventListener("submit",m=>m.preventDefault()),et.addEventListener("input",m=>{st(m),this._liveValidateSettings(t)}),et.addEventListener("change",m=>{st(m),this._liveValidateSettings(t)}),et.addEventListener("click",m=>{const f=m.target.closest(".wd-conflict-fix");if(!f)return;const S=f.dataset.ckey,b=parseFloat(f.dataset.cval),E=et.querySelector(`[data-opt="${S}"]`);E&&!isNaN(b)&&(E.value=b,this._dirtyOptKeys.add(S),this._cascadeConflictFix(t,et,S))}),this._liveValidateSettings(t));const J=t.getElementById("wd-ml-form");J&&(J.addEventListener("input",st),J.addEventListener("change",st));const tt=t.getElementById("wd-settings-revert");tt&&tt.addEventListener("click",async()=>{if(!this._prevOpts)return;const m=this._devices[this._selIdx];m&&await this._busyRun("save-settings",async()=>{try{const f=this._prevOpts;await this._ws({type:`${y}/set_options`,entry_id:m.entry_id,options:f}),this._opts={...f},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(f){this._showToast(this._t("msg.toast_revert_failed",{error:f.message||f},"Revert failed: "+(f.message||f)),"error")}})});const wt=t.getElementById("wd-settings-reload");wt&&wt.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 f=await this._ws({type:`${y}/get_options`,entry_id:m.entry_id});this._opts=f.options||{},this._optDefaults=f.defaults||{},await this._fetchSuggestions(m.entry_id),this._render()}});const U=t.getElementById("wd-pref-fontscale");if(U){const m=t.getElementById("wd-pref-fontscale-val");U.addEventListener("input",()=>{const f=parseFloat(U.value)||1;this._applyFontScale(f),m&&(m.textContent=Math.round(f*100)+"%")}),U.addEventListener("change",()=>{this._setPref("font_scale",parseFloat(U.value)||1)})}t.querySelectorAll("[data-action]").forEach(m=>m.addEventListener("click",f=>this._onAction(f.currentTarget))),t.querySelectorAll("[data-maction]").forEach(m=>m.addEventListener("click",f=>this._onModalAction(f.currentTarget.dataset.maction,f.currentTarget))),t.querySelectorAll("select[data-maction]").forEach(m=>m.addEventListener("change",f=>this._onModalAction(f.currentTarget.dataset.maction,f.currentTarget))),t.querySelectorAll("input[data-indeterminate]").forEach(m=>{m.indeterminate=!0});const ht=t.querySelector("[data-toast-undo]");ht&&ht.addEventListener("click",()=>this._undoDelete(ht.dataset.toastUndo)),t.querySelectorAll(".wd-create-cluster").forEach(m=>m.addEventListener("click",()=>{const f=m.dataset.name||"";this._modal={type:"create-profile",prefillName:f},this._render()})),t.querySelectorAll("[data-suglock]").forEach(m=>m.addEventListener("click",async()=>{const f=m.dataset.suglock,S=this._devices[this._selIdx],b=S&&S.entry_id;if(!(!b||!f))try{if(await this._ws({type:`${y}/set_suggestion_lock`,entry_id:b,key:f,locked:!0}),!this._isActiveEntry(b))return;this._suggestions=(this._suggestions||[]).filter(E=>E.key!==f),(this._lockedSuggestions||[]).includes(f)||(this._lockedSuggestions=this._lockedSuggestions||[]).push(f),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 f=m.dataset.sugkey,S=m.dataset.sugval,b=parseFloat(S);this._pendingSettings[f]=isNaN(b)?S:b,this._dirtyOptKeys.add(f),this._stagedSuggestions=!0,this._suggestions=this._suggestions.filter(N=>N.key!==f),this._showToast(this._t("msg.sug_staged",{key:f,val:S},`Set ${f} = ${S}. Save to apply.`),"info"),this._render();const E=this.shadowRoot,W=E?.getElementById("wd-settings-form");W&&this._cascadeConflictFix(E,W,f)}));const Z=t.getElementById("wd-label-profile");Z&&Z.addEventListener("change",()=>{const m=t.getElementById("wd-new-profile-row"),f=Z.value==="__create_new__";if(m&&(m.style.display=f?"":"none"),!f){const S=t.getElementById("wd-new-profile-name");S&&(S.value="")}});const ot=t.getElementById("wd-cp-cycle");if(ot){const m=()=>{const f=t.getElementById("wd-cp-dur");if(!f)return;const S=!!ot.value;f.disabled=S,f.style.opacity=S?"0.5":""};ot.addEventListener("change",m),m()}const pt=t.getElementById("wd-pr-mode");pt&&pt.addEventListener("change",()=>{const m=t.getElementById("wd-pr-profile"),f=t.getElementById("wd-pr-existing");if(!m||!f)return;const S=pt.value==="existing_profile";m.style.display=S?"none":"",f.style.display=S?"":"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",r=t.getElementById("wd-trim-start"),n=t.getElementById("wd-trim-end");r&&(r.value=s?this._offsetToClock(e.trim.start):Math.round(e.trim.start)),n&&(n.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=r=>e.reduce((n,a)=>Math.abs(a[0]-r)n[0]===t.trim.start);r>=0&&r+10&&(t.trim.start=e[r-1][0])}}_offsetToClock(t){const e=this._modal,s=e&&e.curve&&e.curve.start_time;if(!s)return"";const r=new Date(new Date(s).getTime()+(t||0)*1e3);return`${String(r.getHours()).padStart(2,"0")}:${String(r.getMinutes()).padStart(2,"0")}:${String(r.getSeconds()).padStart(2,"0")}`}_clockToOffset(t){const e=this._modal,s=e&&e.curve&&e.curve.start_time;if(!s||!t)return null;const r=String(t).split(":").map(Number);if(!r.length||r.some(l=>!Number.isFinite(l)))return null;const n=new Date(s),a=new Date(n);a.setHours(r[0]||0,r[1]||0,r[2]||0,0);let o=(a-n)/1e3;const i=e.curve&&e.curve.full_duration_s||0;return o<-1&&o+86400<=i&&(o+=86400),Math.max(0,Math.min(i,o))}_trimInputToOffset(t){if(t===""||t==null)return null;if(this._modal.timeMode==="clock")return this._clockToOffset(t);const e=ie(t,NaN);return Number.isFinite(e)?e:null}_toggleSplit(t){const e=this._modal,s=e.curve&&e.curve.full_duration_s||0,r=Math.max(20,s*.025),n=e.split.offsets,a=n.findIndex(o=>Math.abs(o-t)=0?n.splice(a,1):n.push(Math.round(t)),n.sort((o,i)=>o-i),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 r=t.getElementById("wd-trim-start"),n=t.getElementById("wd-trim-end");r&&r.addEventListener("input",()=>{const i=this._trimInputToOffset(r.value);i!==null&&(e.trim.start=Math.max(0,Math.min(i,e.trim.end-1)),this._drawCycleEditor())}),n&&n.addEventListener("input",()=>{const i=this._trimInputToOffset(n.value);i!==null&&(e.trim.end=Math.min(e.curve.full_duration_s,Math.max(i,e.trim.start+1)),this._drawCycleEditor())});const a=()=>{this._snapTrimBounds(),this._syncTrimInputs(),this._drawCycleEditor()};r&&r.addEventListener("change",a),n&&n.addEventListener("change",a);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",i=>{const l=s._wd;if(!l)return;const c=s.getBoundingClientRect(),p=i.clientX-c.left,_=Math.abs(p-l.xToCss(e.trim.start)),u=Math.abs(p-l.xToCss(e.trim.end));if(i.pointerType==="touch"&&Math.min(_,u)>Dt){e.drag=null;return}e.drag=_<=u?"start":"end",s._wdOwnGesture=!0,s.setPointerCapture(i.pointerId)}),s.addEventListener("pointermove",i=>{if(!e.drag||s._wdPinching)return;const l=s._wd;if(!l)return;const c=s.getBoundingClientRect(),p=l.cssToX(i.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",r=>{const n=s._wd;if(!n||s._wdPinching)return;const a=s.getBoundingClientRect();this._toggleSplit(n.cssToX(r.clientX-a.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 r=()=>{const n=+s.dataset.phidx,a=s.dataset.phfield,o=e.phases[n];o&&(a==="name"?o.name=s.value:o[a]=Math.max(0,ie(s.value,0)*60),this._drawPhaseEditor())};s.addEventListener("input",r),s.addEventListener("change",r)})}_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 r=e.env.target_duration||e.env.avg[e.env.avg.length-1][0],n=Math.max(5,r*.01),a=(i,l)=>{const c=s._wd;if(!c)return null;let p=null,_=l==="touch"?Dt:12;return(e.phases||[]).forEach((u,g)=>{[["start",u.start],["end",u.end]].forEach(([k,$])=>{const x=Math.abs(i-c.xToCss($));x<_&&(_=x,p={idx:g,edge:k})})}),p},o=()=>{e.phaseDrag=null,s._wdOwnGesture=!1};s._wdAbortEdit=o,s.addEventListener("pointerdown",i=>{const l=s.getBoundingClientRect();e.phaseDrag=a(i.clientX-l.left,i.pointerType),e.phaseDrag&&(s._wdOwnGesture=!0,s.setPointerCapture(i.pointerId))}),s.addEventListener("pointermove",i=>{if(!e.phaseDrag||s._wdPinching)return;const l=s._wd;if(!l)return;const c=s.getBoundingClientRect(),p=l.cssToX(i.clientX-c.left),_=e.phases[e.phaseDrag.idx];_&&(e.phaseDrag.edge==="start"?_.start=Math.max(0,Math.min(p,_.end-n)):_.end=Math.min(r,Math.max(p,_.start+n)),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 r=e.querySelector(`[data-phidx="${t}"][data-phfield="start"]`),n=e.querySelector(`[data-phidx="${t}"][data-phfield="end"]`);r&&(r.value=(s.start/60).toFixed(1)),n&&(n.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(r=>r.addEventListener("change",()=>{const n=e.cleanup.cycles[+r.dataset.cleanidx];if(!n)return;r.checked?e.cleanup.selected.add(n.cycle_id):e.cleanup.selected.delete(n.cycle_id);const a=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 (${a.size})`],["pp-cleanup-del","btn.delete_selected",`Delete selected (${a.size})`]];for(const[i,l,c]of o){const p=t.querySelector(`[data-maction="${i}"]`);p&&(p.disabled=a.size===0,p.textContent=this._t(l,{n:a.size},c))}}this._drawSpaghetti()})),t.querySelectorAll("tr[data-cid]").forEach(r=>{r.addEventListener("mouseenter",()=>{this._spagTableHoverCid=r.dataset.cid,this._drawSpaghetti()}),r.addEventListener("mouseleave",()=>{this._spagTableHoverCid=null,this._drawSpaghetti()})});const s=t.getElementById("wd-spag-canvas");s&&s.addEventListener("pointerdown",r=>{if(s._wdPinching)return;this._onGraphHoverInner(r.clientX,r.clientY,"wd-spag-canvas",{touch:r.pointerType==="touch"});const n=this._hoverNearest;if(n&&n.cid){const a=e.cleanup.selected;a.has(n.cid)?a.delete(n.cid):a.add(n.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}const r=this._devices[this._selIdx];if(!r)return;const n=r.entry_id;if(e.startsWith("sug-"))return this._onActSuggestions(e,t,r,n,s);if(e.startsWith("ml-"))return this._onActMl(e,t,r,n);if(e.startsWith("store-"))return this._onActStore(e,t,r,n,s);if(e.startsWith("auto-"))return this._onActAuto(e,t,r,n,s);if(e.startsWith("maint-"))return this._onActMaintenance(e,t,r,n,s);if(e.startsWith("pg-")&&e!=="pg-new"&&e!=="pg-edit"&&e!=="pg-suggest")return this._onActPlayground(e,t,r,n);if(e==="open-cycle"){const a=t.dataset.cid,o=t.dataset.mode==="review"?"review":"view";this._modal={type:"cycle-detail",entryId:n,cycleId:a,loaded:!1,mode:o,curve:null,ml:(this._mlById||{})[a]||null,trim:{start:0,end:0},split:{offsets:[],profiles:[]},drag:null},this._profiles.length||this._fetchProfiles(n),this._render(),this._ws({type:`${y}/get_cycle_power_data`,entry_id:n,cycle_id:a}).then(i=>{this._modal&&this._modal.cycleId===a&&(this._modal.curve=i,this._modal.loaded=!0,this._modal.trim={start:0,end:i.full_duration_s||0},this._render(),i.profile_name&&this._fetchCycleProfileEnv(n,i.profile_name))}).catch(i=>this._showToast(this._t("toast.could_not_load_cycle",{error:i.message||i},"Could not load cycle: "+(i.message||i)),"error"))}else if(e==="cleanup-edit-cycle"){const a=t.dataset.cid;this._prevModal=this._modal,this._modal={type:"cycle-detail",entryId:n,cycleId:a,loaded:!1,mode:"view",curve:null,ml:(this._mlById||{})[a]||null,trim:{start:0,end:0},split:{offsets:[],profiles:[]},drag:null},this._profiles.length||this._fetchProfiles(n),this._render(),this._ws({type:`${y}/get_cycle_power_data`,entry_id:n,cycle_id:a}).then(o=>{this._modal&&this._modal.cycleId===a&&(this._modal.curve=o,this._modal.loaded=!0,this._modal.trim={start:0,end:o.full_duration_s||0},this._render(),o.profile_name&&this._fetchCycleProfileEnv(n,o.profile_name))}).catch(o=>this._showToast(this._t("toast.could_not_load_cycle",{error:o.message||o},"Could not load cycle: "+(o.message||o)),"error"))}else if(e==="open-profile"){const a=t.dataset.pname;this._prevModal=null;const o=(this._profiles||[]).find(i=>i.name===a)||{name:a};this._modal={type:"profile-panel",name:a,tab:"stats",loaded:!1,stats:o,env:null,phases:[],catalog:[],cleanup:null},this._render(),Promise.all([this._ws({type:`${y}/get_profile_envelope`,entry_id:n,profile_name:a}).catch(()=>({envelope:null})),this._ws({type:`${y}/get_profile_phases`,entry_id:n,profile_name:a}).catch(()=>({phases:[]})),this._ws({type:`${y}/get_phase_catalog`,entry_id:n}).catch(()=>({phases:[]}))]).then(([i,l,c])=>{!this._modal||this._modal.name!==a||(this._modal.env=i.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==="setup-cta"){const a=t.dataset.ctaAction||"";let o={};try{o=JSON.parse(t.dataset.ctaParams||"{}")}catch{}this._dispatchSetupCta(a,o)}else if(e==="setup-skip"){const a=t.dataset.step,o=t.dataset.snooze;if(a){let i;if(o==="never")i="never";else{const l=new Date;l.setDate(l.getDate()+14),i=l.toISOString()}this._setPref(a,i),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-level"){const a=(t.type==="checkbox"?t.checked:t.dataset.slevel==="advanced")?"advanced":"basic";a!==this._pref("settings_level","basic")&&(this._snapshotFormToPending(s),this._setPref("settings_level",a),this._render())}else if(e==="pg-new"||e==="pg-edit"||e==="pg-suggest"){if(e==="pg-new")this._modal={type:"profile-group",orig:null,name:"",members:[]};else if(e==="pg-edit"){const a=t.dataset.gname,o=((this._profileGroups||{}).groups||[]).find(i=>i.name===a);this._modal={type:"profile-group",orig:a,name:a,members:o?[...o.members||[]]:[]}}else{const a=((this._profileGroups||{}).suggestions||[])[parseInt(t.dataset.idx,10)]||null;if(!a)return;this._modal={type:"profile-group",orig:a.existing_group||null,name:a.existing_group||"",members:[...a.members||[]]}}this._render(),this._ensureProfileEnvs(n,(this._profiles||[]).map(a=>a.name)).then(()=>{this._modal&&this._modal.type==="profile-group"&&this._render()})}else if(e==="rebuild-envelopes")this._kickAndTrack({type:`${y}/rebuild_envelopes`,entry_id:n},"rebuild-envelopes",async()=>{this._showToast(this._t("toast.envelopes_rebuilt",{},"Envelopes rebuilt")),await this._fetchProfiles(n)});else if(e==="rec-start")this._ws({type:`${y}/start_recording`,entry_id:n}).then(()=>(this._showToast(this._t("toast.recording_started",{},"Recording started")),this._fetchRecState(n))).then(()=>this._render()).catch(a=>this._showToast(this._t("toast.start_failed",{error:a.message||a},"Start failed: "+(a.message||a)),"error"));else if(e==="rec-stop")this._ws({type:`${y}/stop_recording`,entry_id:n}).then(()=>(this._showToast(this._t("toast.recording_stopped",{},"Recording stopped")),this._fetchRecState(n))).then(()=>this._render()).catch(a=>this._showToast(this._t("toast.stop_failed",{error:a.message||a},"Stop failed: "+(a.message||a)),"error"));else if(e==="rec-process-open")this._fetchProfiles(n).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:`${y}/discard_recording`,entry_id:n}),this._showToast(this._t("toast.recording_discarded",{},"Recording discarded")),await this._fetchRecState(n)}catch(a){this._showToast(this._t("toast.discard_failed",{error:a.message||a},"Discard failed: "+(a.message||a)),"error")}}},this._render();else if(e==="fb-confirm")this._ws({type:`${y}/resolve_feedback`,entry_id:n,cycle_id:t.dataset.cid,action:"confirm"}).then(()=>(this._showToast(this._t("toast.feedback_confirmed",{},"Feedback confirmed")),this._fetchFeedbacks(n))).then(()=>this._render()).catch(a=>this._showToast(this._t("msg.toast_error",{error:a.message||a},"Error: "+(a.message||a)),"error"));else if(e==="fb-ignore")this._ws({type:`${y}/resolve_feedback`,entry_id:n,cycle_id:t.dataset.cid,action:"ignore"}).then(()=>(this._showToast(this._t("toast.feedback_dismissed",{},"Feedback dismissed")),this._fetchFeedbacks(n))).then(()=>this._render()).catch(a=>this._showToast(this._t("msg.toast_error",{error:a.message||a},"Error: "+(a.message||a)),"error"));else if(e==="fb-correct")this._fetchProfiles(n).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._t("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:`${y}/dismiss_all_feedbacks`,entry_id:n}),this._showToast(this._t("toast.feedback_all_dismissed",{},"All feedbacks dismissed")),await this._fetchFeedbacks(n)}catch(a){this._showToast(this._t("msg.toast_error",{error:a.message||a},"Error: "+(a.message||a)),"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 a=t.dataset.pname,o=t.dataset.pid;this._modal={type:"confirm",title:this._t("modal.delete_phase_title",{},"Delete Phase"),message:this._t("modal.delete_phase_msg",{name:a},`Delete phase "${a}"?`),okLabel:this._t("btn.delete",{},"Delete"),onOk:async()=>{try{await this._ws({type:`${y}/delete_phase`,entry_id:n,phase_id:o}),this._showToast(this._t("toast.phase_deleted",{name:a},`Phase "${a}" deleted`)),await this._fetchPhases(n)}catch(i){this._showToast(this._t("msg.toast_delete_failed",{error:i.message||i},"Delete failed: "+(i.message||i)),"error")}}},this._render()}else if(e==="diag-refresh")this._fetchToolsData(n).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:`${y}/reprocess_history`,entry_id:n},"reprocess",async a=>{const o=a.count||0,i=[this._t("toast.processed_cycles",{n:o},o+" cycles")];a.suggestions!=null&&i.push(this._t("toast.processed_suggestions",{n:a.suggestions},a.suggestions+" suggestion(s)"));const l=a.ml_training&&a.ml_training.ok&&(a.ml_training.promoted||[]).length||0;l&&i.push(this._t("toast.processed_models",{n:l},l+" model(s) promoted")),this._showToast(this._t("toast.processed",{bits:i.join(", ")},"Processed "+i.join(", "))),await this._fetchToolsData(n)})},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("status.clear",{},"Clear"),onOk:()=>this._busyRun("clear-debug",async()=>{try{const a=await this._ws({type:`${y}/clear_debug_data`,entry_id:n});this._showToast(this._t("toast.debug_cleared",{count:a.count||0},`Cleared ${a.count||0} debug traces`)),await this._fetchToolsData(n)}catch(a){this._showToast(this._t("msg.toast_error",{error:a.message||a},"Error: "+(a.message||a)),"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:`${y}/wipe_history`,entry_id:n}),this._showToast(this._t("toast.all_wiped",{},"All data wiped")),this._cycles=[],this._profiles=[],await this._fetchToolsData(n)}catch(a){this._showToast(this._t("msg.toast_error",{error:a.message||a},"Error: "+(a.message||a)),"error")}})},this._render();else if(e==="export-config")this._ws({type:`${y}/export_config`,entry_id:n}).then(a=>{const o=new Blob([a.json_data],{type:"application/json"}),i=URL.createObjectURL(o),l=document.createElement("a");l.href=i,l.download=`washdata_export_${n.slice(0,8)}.json`,document.body.appendChild(l),l.click(),document.body.removeChild(l),URL.revokeObjectURL(i),this._showToast(this._t("toast.export_downloaded",{},"Export downloaded"))}).catch(a=>this._showToast(this._t("toast.export_failed",{error:a.message||a},"Export failed: "+(a.message||a)),"error"));else if(e==="export-select-open")this._modal={type:"export-select",inventory:null,loading:!0,sel:{cats:new Set,profiles:new Set,realIds:new Set,refIds:new Set},expanded:new Set},this._render(),(async()=>{let a=null;try{const o=await this._ws({type:`${y}/get_export_inventory`,entry_id:n});a=o&&o.manifest||null}catch(o){this._showToast(this._t("toast.export_failed",{error:o.message||o},"Export failed: "+(o.message||o)),"error")}if(!(!this._isActiveEntry(n)||!this._modal||this._modal.type!=="export-select")){if(!a){this._modal=null,this._render();return}this._modal.inventory=a,this._modal.sel=this._wizInitSel({categories:a},!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._modal={type:"auto-label"},this._render();else if(e==="cyc-merge"){const a=Array.from(this._cycleSel);if(a.length<2)return;this._fetchProfiles(n).then(()=>{this._modal={type:"merge-cycles",ids:a},this._render()})}else if(e==="cyc-relabel"){const a=Array.from(this._cycleSel);if(!a.length)return;this._fetchProfiles(n).then(()=>{this._modal={type:"bulk-relabel",ids:a},this._render()})}else if(e==="cyc-load-more")this._busyRun("cyc-load-more",async()=>{try{await this._loadMoreCycles(n)}catch(a){this._showToast(this._t("toast.load_more_failed",{error:a.message||a},"Could not load more: "+(a.message||a)),"error")}});else if(e==="task-cancel"){const a=t.dataset.taskId;a&&(this._cancellingTasks.add(a),this._updateTaskPills(),this._ws({type:`${y}/cancel_task`,task_id:a}).catch(()=>{this._cancellingTasks.delete(a),this._updateTaskPills()}))}else if(e==="cyc-compare"){const a=Array.from(this._cycleSel);if(a.length<2)return;this._modal={type:"compare-cycles",ids:a,cycles:{},hidden:new Set,overlays:[],loaded:!1},this._profiles.length||this._fetchProfiles(n),this._render(),Promise.all(a.map(o=>this._ws({type:`${y}/get_cycle_power_data`,entry_id:n,cycle_id:o}).then(i=>({cid:o,r:i})).catch(()=>({cid:o,r:null})))).then(o=>{!this._modal||this._modal.type!=="compare-cycles"||(o.forEach(({cid:i,r:l})=>{l&&(this._modal.cycles[i]=l)}),this._modal.loaded=!0,this._render())})}else if(e==="cyc-bulk-del"){const a=Array.from(this._cycleSel);if(!a.length)return;this._deleteCyclesWithUndo(n,a)}else if(e==="retry-cycles")this._fetchCycles(n).then(()=>this._render());else if(e==="retry-profiles")Promise.all([this._fetchProfiles(n),this._fetchProfileGroups(n)]).then(()=>this._render());else if(e==="retry-suggestions")this._fetchSuggestions(n).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==="conf-goto-section"){const a=this._conflictKeysFromOpts();for(const o of xt)if((o.fields||(o.groups||[]).flatMap(l=>l.fields||[])).some(l=>a.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 a=t.dataset.key,o=JSON.parse(t.dataset.val);if(!a)return;const i=r.entry_id;this._ws({type:`${y}/set_options`,entry_id:i,options:{[a]:o}}).then(()=>this._ws({type:`${y}/get_options`,entry_id:i})).then(l=>(this._opts=l.options||{},this._optDefaults=l.defaults||{},this._fetchSettingsChangelog(i))).then(()=>{this._showToast(this._t("msg.toast_reverted",{key:this._t("setting."+a+".label",{},a)},"{key} reverted"),"success"),this._render()}).catch(l=>this._showToast(this._t("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==="open-advanced"){const a=t.dataset.sub;a&&(this._panelSubtab=a),this._tab="advanced",this._render(),this._panelSubtab==="diagnostics"&&!this._diag?this._fetchToolsData(n).then(()=>{this._tab==="advanced"&&this._render()}):this._panelSubtab==="logs"?this._fetchLogs().then(()=>{this._tab==="advanced"&&this._render()}):this._panelSubtab==="maintenance"?this._fetchMaintenance(n).then(()=>{this._tab==="advanced"&&this._render()}):this._panelSubtab==="ml"&&this._fetchTabData()}else if(e==="add-device")this._navigate(`/config/integrations/integration/${y}`);else if(e==="goto-feedbacks")this._tab="history",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:`${y}/get_logs`,limit:500}).then(a=>{const o=(a.logs||[]).map(p=>`${new Date(p.ts*1e3).toISOString()} ${p.level} ${p.msg}`).join(`
`),i=new Blob([o],{type:"text/plain"}),l=URL.createObjectURL(i),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(a=>this._showToast(this._t("toast.export_failed",{error:a.message||a},"Export failed: "+(a.message||a)),"error"));else if(e==="import-config-open")this._modal={type:"import-wizard",step:"input",jsonText:"",manifest:null,error:null,sel:{cats:new Set,profiles:new Set,realIds:new Set,refIds:new Set},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:jt(),token:null,scanTaskId:null,applyTaskId:null,result:null,accept:new Set,done:null,error:null},this._render();else if(e==="import-config-raw")this._modal={type:"import-config"},this._render();else if(e==="save-prefs"){const a=s.getElementById("wd-pref-tab")?.value||"",o=!!s.getElementById("wd-pref-debug")?.checked,i=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,u={default_tab:a,show_debug:o,show_expected:i,show_raw:l,date_format:c,lang_override:p,font_scale:_};this._busyRun("save-prefs",async()=>{try{await this._ws({type:`${y}/set_user_prefs`,prefs:u}),this._panelCfg&&(this._panelCfg.prefs={...this._panelCfg.prefs||{},...u});const g=p||this._hass&&this._hass.locale&&this._hass.locale.language;await this._loadPanelLang(g),this._render(),this._showToast(this._t("toast.preferences_saved",{},"Preferences saved"))}catch(g){this._showToast(this._t("toast.save_failed",{error:g.message||g},"Save failed: "+(g.message||g)),"error")}})}else if(e==="save-panel"){const a={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:`${y}/set_panel_config`,panel:a}),this._panelCfg=await this._ws({type:`${y}/get_panel_config`}),this._tabInitialized=!0,this._applyPanelConfig(),this._showToast(this._t("toast.panel_settings_saved",{},"Panel settings saved"))}catch(o){this._showToast(this._t("msg.toast_save_failed",{error:o.message||o},"Save failed: "+(o.message||o)),"error")}})}else if(e==="pause-cycle")this._ws({type:`${y}/pause_cycle`,entry_id:n}).then(a=>{if(a&&a.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(a=>this._showToast(this._t("toast.pause_failed",{error:a.message||a},"Pause failed: "+(a.message||a)),"error"));else if(e==="resume-cycle")this._ws({type:`${y}/resume_cycle`,entry_id:n}).then(a=>{if(a&&a.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(a=>this._showToast(this._t("msg.toast_resume_failed",{error:a.message||a},"Resume failed: "+(a.message||a)),"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:`${y}/terminate_cycle`,entry_id:n}),this._showToast(this._t("toast.cycle_force_stopped",{},"Cycle force-stopped")),await this._fetchAll()}catch(a){this._showToast(this._t("msg.toast_force_stop_failed",{error:a.message||a},"Force stop failed: "+(a.message||a)),"error")}}},this._render();else if(e==="save-rbac"){const a=!!s.getElementById("wd-rbac-enabled")?.checked,o=s.getElementById("wd-rbac-default")?.value||"none",i={};s.querySelectorAll("[data-rbacuser]").forEach(l=>{const c=l.dataset.rbacuser,p=l.dataset.rbacdev,_=l.value;i[c]||(i[c]={default:"none",devices:{}}),p==="__default__"?i[c].default=_:_&&_!=="inherit"&&(i[c].devices[p]=_)}),this._busyRun("save-rbac",async()=>{try{await this._ws({type:`${y}/set_panel_config`,rbac:{enabled:a,default_level:o,users:i}}),this._panelCfg=await this._ws({type:`${y}/get_panel_config`}),this._showToast(this._t("toast.access_saved",{},"Access control saved"))}catch(l){this._showToast(this._t("msg.toast_save_failed",{error:l.message||l},"Save failed: "+(l.message||l)),"error")}})}}_onActSuggestions(t,e,s,r,n){if(t==="sug-apply-all"){const a=this._suggestions.map(o=>o.key);this._busyRun("save-settings",async()=>{try{await this._ws({type:`${y}/apply_suggestions`,entry_id:r,keys:a}),this._showToast(this._t("toast.suggestions_applied",{},"Suggestions applied; integration reloading")),await this._fetchSuggestions(r);const o=await this._ws({type:`${y}/get_options`,entry_id:r});this._opts=o.options||{},this._optDefaults=o.defaults||{},this._prevOpts=null,this._cascadePending={},this._preCascadeOpts=null}catch(o){this._showToast(this._t("toast.apply_failed",{error:o.message||o},"Apply failed: "+(o.message||o)),"error")}})}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:`${y}/clear_suggestions`,entry_id:r}),this._suggestions=[],this._showToast(this._t("toast.suggestions_dismissed",{},"Suggestions dismissed"))}catch(a){this._showToast(this._t("toast.error",{error:a.message||a},"Error: "+(a.message||a)),"error")}})):t==="sug-unmute-all"?this._busyRun("save-settings",async()=>{try{const a=[...this._lockedSuggestions||[]],o=await Promise.allSettled(a.map(c=>this._ws({type:`${y}/set_suggestion_lock`,entry_id:r,key:c,locked:!1})));if(!this._isActiveEntry(r))return;const i=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(r),i?this._showToast(this._t("toast.error",{error:`${i} suggestion(s) failed to unlock`},`${i} suggestion(s) failed to unlock`),"error"):this._showToast(this._t("msg.sug_unmuted_all",{},"Muted suggestions reset"),"success")}catch(a){this._showToast(this._t("toast.error",{error:a.message||a},"Error: "+(a.message||a)),"error")}}):t==="sug-analyze"&&this._busyRun("sug-analyze",async()=>{try{const a=await this._ws({type:`${y}/run_suggestion_analysis`,entry_id:r}),o=a&&a.count||0;this._showToast(o?this._t("toast.analysis_complete",{count:o},`Analysis complete: ${o} suggestion(s)`):this._t("toast.analysis_complete_none",{},"Analysis complete: no new suggestions")),await this._fetchSuggestions(r)}catch(a){this._showToast(this._t("toast.analysis_failed",{error:a.message||a},"Analysis failed: "+(a.message||a)),"error")}})}_onActMl(t,e,s,r){t==="ml-train-now"?this._kickAndTrack({type:`${y}/trigger_ml_training`,entry_id:r},"ml-train-now:"+r,async n=>{if(n&&n.ok){const a=(n.promoted||[]).length;this._showToast(a?this._t("toast.ml_training_promoted",{count:a},`Training complete: promoted ${a} model(s)`):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(r)}):t==="ml-revert-match"?this._busyRun("ml-revert-match",async()=>{try{await this._ws({type:`${y}/revert_matching_config`,entry_id:r}),this._showToast(this._t("toast.matching_reverted",{},"Matching weights reverted to defaults")),await this._loadMlTrainingStatus(r)}catch(n){this._showToast(this._t("toast.revert_failed",{error:n.message||n},"Revert failed: "+(n.message||n)),"error")}}):t==="ml-revert-models"&&this._busyRun("ml-revert-models",async()=>{try{await this._ws({type:`${y}/revert_ml_models`,entry_id:r}),this._showToast(this._t("toast.models_reverted",{},"On-device models reverted to baseline")),await this._loadMlTrainingStatus(r)}catch(n){this._showToast(this._t("msg.toast_revert_failed",{error:n.message||n},"Revert failed: "+(n.message||n)),"error")}})}_onActStore(t,e,s,r,n){if(t==="store-toggle-online"){const a=!!e.checked;this._busyRun("store-account",async()=>{try{const o=await this._ws({type:`${y}/store_set_online`,entry_id:r,enabled:a});this._constants.storeOnlineEnabled=!!(o&&o.enabled),this._constants.storeOnlineEnabled?(await this._loadStoreStatus(r),this._ensureStoreConnectListener()):(this._storeStatus={enabled:!1},this._storeConnected=!1)}catch(o){this._showToast(this._t("toast.store_error",{error:o.message||o},"Error: "+(o.message||o)),"error")}})}else if(t==="store-toggle-pref"){const a=e.dataset.pref,o=!!e.checked;if(!a)return;this._busyRun("store-account",async()=>{try{const i=await this._ws({type:`${y}/store_set_prefs`,entry_id:r,prefs:{[a]:o}});i&&i.prefs&&(this._constants={...this._constants,storePrefs:i.prefs})}catch(i){this._showToast(this._t("toast.store_error",{error:i.message||i},"Error: "+(i.message||i)),"error")}})}else if(t==="store-goto-identity")this._tab="settings",this._settingsSec="basic",this._fetchTabData(),requestAnimationFrame(()=>requestAnimationFrame(()=>{const a=this.shadowRoot&&this.shadowRoot.getElementById("wd-store-brand");a&&(a.focus(),a.scrollIntoView({block:"center"}))}));else if(t==="store-refresh-catalog")this._busyRun("store-refresh-catalog",async()=>{try{await this._ws({type:`${y}/store_refresh_catalog`,entry_id:r}),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(a){this._showToast(this._t("toast.store_error",{error:a.message||a},"Error: "+(a.message||a)),"error")}});else if(t==="store-connect"){const a=this._constants.storeWebOrigin;if(!a){this._showToast(this._t("toast.store_unavailable",{},"The community store is not available."),"error");return}this._ensureStoreConnectListener(),window.open(a+"/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:`${y}/store_disconnect`,entry_id:r}),await this._loadStoreStatus(r),this._showToast(this._t("toast.store_disconnected",{},"Disconnected from the community store"))}catch(a){this._showToast(this._t("toast.store_error",{error:a.message||a},"Error: "+(a.message||a)),"error")}});else if(t==="store-add-appliance"){const a=this._constants.storeWebOrigin;if(!a){this._showToast(this._t("toast.store_unavailable",{},"The community store is not available."),"error");return}this._ensureStoreConnectListener();const o=n.getElementById("wd-store-model"),i=n.getElementById("wd-store-brand"),l=new URLSearchParams({mode:"device",type:this._storeApplianceType(),brand:i&&i.value||this._opts.store_brand||"",model:o&&o.value||this._opts.store_model||"",origin:location.origin}).toString();window.open(a+"/create.html?"+l,"washdata_create","width=560,height=760")}else if(t==="store-add-brand"){const a=this._constants.storeWebOrigin;if(!a){this._showToast(this._t("toast.store_unavailable",{},"The community store is not available."),"error");return}this._ensureStoreConnectListener();const o=n.getElementById("wd-store-brand"),i=new URLSearchParams({mode:"brand",brand:o&&o.value||this._opts.store_brand||"",origin:location.origin}).toString();window.open(a+"/create.html?"+i,"washdata_create","width=560,height=760")}else if(t==="store-confirm-device"){const a=e.dataset.deviceId;this._busyRun("store-account",async()=>{try{const o=await this._ws({type:`${y}/store_confirm_device`,entry_id:r,device_id:a});if(o&&o.error){this._showToast(this._t("toast.store_error",{error:o.error},"Error: "+o.error),"error");return}const i=[...this._catalog.devices||[],...this._storeDevices||[],this._catalogEntry&&this._catalogEntry.device||null];for(const l of i)o&&l&&String(l.id)===String(a)&&(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._t("toast.store_error",{error:o.message||o},"Error: "+(o.message||o)),"error")}})}else if(t==="store-rate-device"){const a=e.dataset.deviceId,o=parseInt(e.dataset.rating,10);if(!(o>=1&&o<=5))return;this._busyRun("store-account",async()=>{try{const i=await this._ws({type:`${y}/store_rate_device`,entry_id:r,device_id:a,rating:o});if(i&&i.error){this._showToast(this._t("toast.store_error",{error:i.error},"Error: "+i.error),"error");return}this._showToast(this._t("toast.rating_saved",{},"Quality rating saved"))}catch(i){this._showToast(this._t("toast.store_error",{error:i.message||i},"Error: "+(i.message||i)),"error")}})}else if(t==="store-nav"){const a=e.dataset.view;a==="brands"?(this._storeView="brands",this._storeDevice=null,this._storeProfile=null,this._render()):a==="device"&&(this._storeView="device",this._storeProfile=null,this._render())}else if(t==="store-open-device"){const a=e.dataset.deviceId,o=(this._storeDevices||[]).find(i=>String(i.id)===String(a));o&&this._storeOpenDevice(o)}else if(t==="store-open-profile"){const a=e.dataset.profileId,o=(this._storeProfiles||[]).find(i=>String(i.id)===String(a));if(!o)return;this._storeProfile=o,this._storeView="profile",this._storeCycles=[],this._storeLoading=!0,this._render(),this._ws({type:`${y}/store_get_cycles`,entry_id:r,profile_id:o.id}).then(i=>{!this._isActiveEntry(r)||this._storeView!=="profile"||(this._storeCycles=i&&i.items||[])}).catch(()=>{this._isActiveEntry(r)&&(this._storeCycles=[])}).finally(()=>{this._isActiveEntry(r)&&(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 a=e.dataset.deviceId;if(!a)return;const o=!!this._dlSettings;this._busyRun("store-download-device",async()=>{try{const i=await this._ws({type:`${y}/store_download_device`,entry_id:r,device_id:a,include_settings:o});if(i&&(i.error||i.disabled)){const u=i.error||"unavailable";this._showToast(this._t("toast.store_download_failed",{error:u},"Download failed: "+u),"error");return}const l=i&&i.profiles_adopted||0,c=i&&i.cycles_imported||0,p=i&&i.settings_applied||0;if(!l&&!c&&!p){this._showToast(this._t("toast.store_download_nothing",{},"Nothing new to download - this setup is already on your device."),"info");return}await this._fetchProfiles(r),await this._fetchCycles(r);const _=i&&i.phases_applied||0;p?this._showToast(this._t("toast.store_device_downloaded_settings",{p:l,c,ph:_,s:p},`${l} program(s), ${c} recording(s), ${_} phase map(s), ${p} setting(s) added`)):_?this._showToast(this._t("toast.store_device_downloaded_phases",{p:l,c,ph:_},`${l} program(s), ${c} recording(s), ${_} phase map(s) added`)):this._showToast(this._t("toast.store_device_downloaded",{p:l,c},`${l} program(s), ${c} recording(s) added`))}catch(i){this._showToast(this._t("toast.store_download_failed",{error:i.message||i},"Download failed: "+(i.message||i)),"error")}})}else if(t==="store-import"){const a=e.dataset.cycleId,o=this._storeProfile&&this._storeProfile.program||"";this._modal={type:"store-import",cycleId:a,program:o,mode:"new"},this._render()}else if(t==="store-share-cycle"){const a=e.dataset.cid,o=e.dataset.prof||"";this._modal={type:"store-share",cycleId:a,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:`${y}/get_shareable_cycles`,entry_id:r});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(r)||!this._modal||this._modal.type!=="store-share-device")return;const a=new Set;this._shareableByProgram().forEach(o=>o.cycles.forEach(i=>a.add(i.id))),this._modal.selected=a,this._modal.includePhases=new Set(this._sharePhasePrograms),this._modal.loading=!1,this._render()})();else if(t==="store-share-profile"){const a=e.dataset.prog||"";this._modal={type:"store-share-device",selected:new Set,includePhases:new Set,includeSettings:!1,loading:!0,focusProfile:a},this._render(),(async()=>{try{const i=await this._ws({type:`${y}/get_shareable_cycles`,entry_id:r});this._shareableCycles=i&&i.items||[],this._sharePhasePrograms=i&&i.phase_programs||[],this._shareAllPrograms=i&&i.all_programs||[]}catch{this._shareableCycles=[],this._sharePhasePrograms=[],this._shareAllPrograms=[]}if(!this._isActiveEntry(r)||!this._modal||this._modal.type!=="store-share-device")return;const o=new Set;this._shareableByProgram().filter(i=>i.program===a).forEach(i=>i.cycles.forEach(l=>o.add(l.id))),this._modal.selected=o,this._modal.includePhases=new Set((this._sharePhasePrograms||[]).filter(i=>i===a)),this._modal.loading=!1,this._render()})()}else if(t==="store-share-add-profile"){const a=this._modal,o=this._constants.storeWebOrigin;if(!o||!a){o||this._showToast(this._t("toast.store_unavailable",{},"The community store is not available."),"error");return}this._ensureStoreConnectListener();const i=new URLSearchParams({mode:"profile",device:a.deviceId||"",type:this._storeApplianceType(),brand:this._opts.store_brand||"",model:this._opts.store_model||"",origin:location.origin}).toString();window.open(o+"/create.html?"+i,"washdata_create","width=560,height=760")}}_onActAuto(t,e,s,r,n){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 a=e.dataset.autoid,o=e.dataset.autoname||"this automation";this._modal={type:"confirm",title:this._t("modal.delete_automation_title",{},"Delete Automation"),message:this._t("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/"+a),this._showToast(this._t("toast.automation_deleted",{},"Automation deleted")),await this._loadDeviceAutomations(r)}catch(i){this._showToast(this._t("toast.delete_failed",{error:i.message||i},"Delete failed: "+(i.message||i)),"error")}}},this._render()}else if(t==="auto-convert-legacy")this._convertLegacyActions();else if(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:`${y}/set_options`,entry_id:r,options:{notify_actions:[]}}),this._opts={...this._opts,notify_actions:[]},this._showToast(this._t("toast.legacy_removed",{},"Legacy actions removed"))}catch(a){this._showToast(this._t("toast.delete_failed",{error:a.message||a},"Remove failed: "+(a.message||a)),"error")}}},this._render();else if(t==="auto-label"){const a=parseFloat(n.getElementById("wd-auto-label-threshold")?.value||"0.75");this._busyRun("auto-label",async()=>{try{await this._ws({type:`${y}/auto_label_cycles`,entry_id:r,confidence_threshold:a}),this._showToast(this._t("toast.auto_label_complete",{},"Auto-label complete")),await this._fetchCycles(r)}catch(o){this._showToast(this._t("toast.auto_label_failed",{error:o.message||o},"Auto-label failed: "+(o.message||o)),"error")}})}}_onActMaintenance(t,e,s,r,n){if(t==="maint-add"){const a=n.getElementById("wd-maint-type")?.value||"",o=n.getElementById("wd-maint-date")?.value||"",i=(n.getElementById("wd-maint-notes")?.value||"").trim();if(!a){this._showToast(this._t("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:`${y}/add_maintenance_event`,entry_id:r,event_type:a};o&&(l.date=o),i&&(l.notes=i),await this._ws(l),await this._fetchMaintenance(r),this._showToast(this._t("toast.maint_added",{},"Maintenance event added")),this._render()}catch(l){this._showToast(this._t("toast.maint_add_failed",{error:l.message||l},"Could not add event: "+(l.message||l)),"error")}})}else if(t==="maint-delete"){const a=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:`${y}/delete_maintenance_event`,entry_id:r,event_id:a}),await this._fetchMaintenance(r),this._showToast(this._t("toast.maint_deleted",{},"Maintenance event deleted"))}catch(o){this._showToast(this._t("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 a=n.getElementById("wd-maint-odometer")?.value,o=parseInt(a,10);if(isNaN(o)||o<0){this._showToast(this._t("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:`${y}/set_lifetime_cycle_count`,entry_id:r,count:o}),await this._fetchMaintenance(r),this._showToast(this._t("toast.odometer_saved",{},"Total cycles saved")),this._render()}catch(i){this._showToast(this._t("toast.odometer_save_failed",{error:i.message||i},"Could not save total: "+(i.message||i)),"error")}})}else if(t==="maint-save-reminders"){const a={};n.querySelectorAll("[data-maint-rem]").forEach(o=>{const i=o.dataset.maintRem,l=parseInt(o.value,10);a[i]=!isNaN(l)&&l>0?l:0}),this._busyRun("maint-save-reminders",async()=>{try{await this._ws({type:`${y}/set_options`,entry_id:r,options:{maintenance_reminder_cycles:a}}),await this._fetchMaintenance(r),this._showToast(this._t("toast.reminders_saved",{},"Service reminders saved")),this._render()}catch(o){this._showToast(this._t("toast.reminders_save_failed",{error:o.message||o},"Could not save reminders: "+(o.message||o)),"error")}})}}_onActPlayground(t,e,s,r){if(t==="pg-analysis-tab"){const n=e.dataset.subtab||"history";n!==this._pgAnalysisTab&&(this._pgAnalysisTab=n,this._render(),requestAnimationFrame(()=>this._drawPlaygroundCanvases()))}else if(t==="pg-run-history")this._pgRunHistory();else if(t==="pg-batch-cancel"){this._pgBatchCancel=!0;const n=this._pgHistoryTaskId||this._pgSweepTaskId;n&&this._ws({type:`${y}/cancel_task`,task_id:n}).catch(()=>{})}else if(t==="pg-load-run"){const n=e.dataset.taskId;if(!n)return;const a=this._tasks[n];if(!a||a.kind!=="pg_history"&&a.kind!=="pg_sweep"){this._showToast(this._t("toast.pg_run_gone",{},"That run is no longer available."),"info");return}const o=a.entry_id;this._ws({type:`${y}/get_task_result`,task_id:n}).then(i=>{if(!this._isActiveEntry(o))return;const l=i&&i.result;if(!l){this._showToast(this._t("toast.pg_run_gone",{},"That run is no longer available."),"info");return}a.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 if(t==="pg-open-cycle")this._pgSelectCycle(e.dataset.cid);else if(t==="pg-sweep-run2")this._pgRunSweep2();else if(t==="pg-sweep-apply2")this._pgApplySweepValue(e.dataset.val);else if(t==="pg-run"||t==="pg-load")this._pgLoad();else if(t==="pg-cancel-run")this._pgCancelRun();else if(t==="pg-reset-params")this._pgThreshStart=null,this._pgThreshStop=null,this._pgParamOverrides={},this._pgStressTail=!1,this._pgStressIdleW=null,this._render(),requestAnimationFrame(()=>this._pgDrawCanvas());else if(t==="pg-apply-settings")this._pgApplyToSettings();else if(t==="pg-load-live")this._pgLoadLive();else if(t==="pg-load-suggested")this._pgLoadSuggested("classic");else if(t==="pg-load-calibrated")this._pgLoadSuggested("ml");else if(t==="pg-preset-save")this._pgSavePreset();else if(t==="pg-preset-load"){const n=(this._pgPresets||[]).find(a=>a.name===this._pgPresetSel);n&&(this._pgApplyPresetValues(n.values),this._showToast(this._t("toast.pg_preset_loaded",{name:n.name},`Preset "${n.name}" loaded`)),this._render(),requestAnimationFrame(()=>this._pgDrawCanvas()))}else t==="pg-preset-delete"?this._pgDeletePreset():t==="pg-publish-one"&&this._pgPublishOne(e.dataset.pgkey)}async _onModalAction(t,e){const s=this.shadowRoot,r=this._devices[this._selIdx],n=r?r.entry_id:null,a=this._modal;if(t==="cancel"){if(a&&a.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"&&a&&a.onOk){const o=a.onOk;this._modal=null,this._render(),await o(),this._render();return}if(a&&a.type==="profile-group"){if(t==="pg-save"){const o=s.getElementById("wd-pg-name")?.value?.trim(),i=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(i.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{a.orig&&a.orig!==o&&await this._ws({type:`${y}/rename_profile_group`,entry_id:n,name:a.orig,new_name:o}),await this._ws({type:`${y}/save_profile_group`,entry_id:n,name:o,members:i}),this._showToast(this._t("toast.group_saved",{},"Group saved")),this._modal=null,await this._fetchProfileGroups(n)}catch(l){this._showToast(this._t("msg.toast_save_failed",{error:l.message||l},"Save failed: "+(l.message||l)),"error")}});return}if(t==="pg-delete"&&a.orig){await this._busyRun("pg-save",async()=>{try{await this._ws({type:`${y}/delete_profile_group`,entry_id:n,name:a.orig}),this._showToast(this._t("toast.group_deleted",{},"Group deleted")),this._modal=null,await this._fetchProfileGroups(n)}catch(o){this._showToast(this._t("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,a,n,s);if(t.startsWith("hist-"))return this._onMActHistoryImport(t,e,a,n,s);if(t.startsWith("sd-")||t==="store-share-device-ok")return this._onMActStoreShare(t,e,a,n);if(a&&a.type==="export-select"&&t==="export-generate"&&n){const o=this._wizSelectionPayload(a);await this._busyRun("export-select",async()=>{try{const i=await this._ws({type:`${y}/export_config_selective`,entry_id:n,selection:o}),l=new Blob([i.json_data],{type:"application/json"}),c=URL.createObjectURL(l),p=document.createElement("a");p.href=c,p.download=`washdata_export_${n.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(i){this._showToast(this._t("toast.export_failed",{error:i.message||i},"Export failed: "+(i.message||i)),"error")}});return}if(t.startsWith("cyc-")||t.startsWith("trim-mode-"))return this._onMActCycleDetail(t,a,n,s);if(a&&a.type==="profile-panel"){if(t.indexOf("pp-tab-")===0){const o=t.slice(7);a.tab=o,this._render(),o==="cleanup"&&!a.cleanup&&this._ws({type:`${y}/get_profile_cycles`,entry_id:n,profile_name:a.name}).then(i=>{this._modal&&this._modal.name===a.name&&(this._modal.cleanup={cycles:i.cycles||[],selected:new Set},this._render())}).catch(()=>{this._modal&&(this._modal.cleanup={cycles:[],selected:new Set},this._render())});return}return this._onMActProfilePanel(t,e,a,n,s)}if(t==="label-ok"&&n){const o=s.getElementById("wd-label-profile"),i=o?o.value:"",l=i||null,c=i==="__create_new__"&&s.getElementById("wd-new-profile-name")?.value?.trim()||null;this._modal=null;try{await this._ws({type:`${y}/label_cycle`,entry_id:n,cycle_id:a.cycleId,profile_name:l||null,new_profile_name:c}),this._showToast(this._t("toast.cycle_labelled",{},"Cycle labelled")),await this._fetchCycles(n),await this._fetchProfiles(n),await this._fetchFeedbacks(n)}catch(p){this._showToast(this._t("toast.label_failed",{error:p.message||p},"Label failed: "+(p.message||p)),"error")}this._render()}else if(t==="create-profile-ok"&&n){const o=s.getElementById("wd-cp-name")?.value?.trim(),i=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=!i&&l>0?l:null;try{await this._ws({type:`${y}/create_profile`,entry_id:n,name:o,reference_cycle:i||null,manual_duration_min:c}),this._showToast(this._t("toast.profile_created",{name:o},`Profile "${o}" created`)),await this._fetchProfiles(n)}catch(p){this._showToast(this._t("toast.create_failed",{error:p.message||p},"Create failed: "+(p.message||p)),"error")}this._render()}else if(t==="create-phase-ok"&&n){const o=s.getElementById("wd-ph-name")?.value?.trim(),i=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:`${y}/create_phase`,entry_id:n,device_type:a.deviceType||"",name:o,description:i}),this._showToast(this._t("toast.phase_created",{name:o},`Phase "${o}" created`)),await this._fetchPhases(n)}catch(l){this._showToast(this._t("msg.toast_create_failed",{error:l.message||l},"Create failed: "+(l.message||l)),"error")}this._render()}else if(t==="edit-phase-ok"&&n){const o=s.getElementById("wd-eph-name")?.value?.trim(),i=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:`${y}/update_phase`,entry_id:n,phase_id:a.phaseId,new_name:o,description:i}),this._showToast(this._t("toast.phase_updated",{},"Phase updated")),await this._fetchPhases(n)}catch(l){this._showToast(this._t("toast.update_failed",{error:l.message||l},"Update failed: "+(l.message||l)),"error")}this._render()}else if(t==="process-rec-ok"&&n){const o=s.getElementById("wd-pr-mode")?.value;let i=s.getElementById("wd-pr-profile")?.value?.trim();o==="existing_profile"&&(i=s.getElementById("wd-pr-profile-sel")?.value||i);const l=parseFloat(s.getElementById("wd-pr-head")?.value||0),c=parseFloat(s.getElementById("wd-pr-tail")?.value||0);if(this._modal=null,!i){this._showToast(this._t("msg.toast_profile_name_required",{},"Profile name is required"),"error"),this._render();return}try{await this._ws({type:`${y}/process_recording`,entry_id:n,profile_name:i,save_mode:o,head_trim:l,tail_trim:c}),this._showToast(this._t("toast.recording_saved",{},"Recording saved to profile")),await this._fetchRecState(n),await this._fetchProfiles(n)}catch(p){this._showToast(this._t("msg.toast_save_failed",{error:p.message||p},"Save failed: "+(p.message||p)),"error")}this._render()}else if(t==="correct-fb-ok"&&n){const o=s.getElementById("wd-fb-profile")?.value,i=parseFloat(s.getElementById("wd-fb-dur")?.value||0)||null;this._modal=null;try{await this._ws({type:`${y}/resolve_feedback`,entry_id:n,cycle_id:a.cycleId,action:"correct",corrected_profile:o,corrected_duration_min:i}),this._showToast(this._t("toast.correction_submitted",{},"Correction submitted")),await this._fetchFeedbacks(n)}catch(l){this._showToast(this._t("msg.toast_error",{error:l.message||l},"Error: "+(l.message||l)),"error")}this._render()}else if(t==="import-ok"&&n){const o=s.getElementById("wd-import-json")?.value;if(this._modal=null,!o?.trim()){this._showToast(this._t("toast.json_required",{},"JSON data is required"),"error"),this._render();return}try{await this._ws({type:`${y}/import_config`,entry_id:n,json_data:o}),this._showToast(this._t("toast.import_successful",{},"Import successful; integration reloading")),await this._fetchCycles(n)}catch(i){this._showToast(this._t("toast.import_failed",{error:i.message||i},"Import failed: "+(i.message||i)),"error")}this._render()}else if(t==="auto-run"&&n){const o=parseFloat(s.getElementById("wd-al-thr")?.value||"0.75");this._modal=null,this._render(),await this._busyRun("auto-label",async()=>{try{await this._ws({type:`${y}/auto_label_cycles`,entry_id:n,confidence_threshold:o}),this._showToast(this._t("msg.toast_auto_label_complete",{},"Auto-label complete")),await this._fetchCycles(n)}catch(i){this._showToast(this._t("msg.toast_auto_label_failed",{error:i.message||i},"Auto-label failed: "+(i.message||i)),"error")}})}else if(t==="merge-ok"&&n){const o=s.getElementById("wd-merge-prof")?.value||"",i=s.getElementById("wd-merge-newname")?.value?.trim()||null,l=a.ids||[];this._modal=null,this._render(),this._kickAndTrack({type:`${y}/apply_merge`,entry_id:n,cycle_ids:l,target_profile:o||null,new_profile_name:i},"cyc-merge",async()=>{this._showToast(this._t("toast.cycles_merged",{},"Cycles merged")),this._cycleSel.clear(),this._selectMode=!1,await this._fetchCycles(n),await this._fetchProfiles(n)})}else if(t==="bulk-relabel-ok"&&n){const o=s.getElementById("wd-relabel-profile")?.value||"",i=(a.ids||[]).slice();if(this._modal=null,this._render(),!i.length)return;await this._busyRun("cyc-relabel",async()=>{try{for(const l of i)await this._ws({type:`${y}/label_cycle`,entry_id:n,cycle_id:l,profile_name:o||null});this._showToast(this._t("toast.relabel_done",{count:i.length},`Relabelled ${i.length} cycle(s)`)),this._cycleSel.clear(),this._selectMode=!1,await this._fetchCycles(n),await this._fetchProfiles(n),await this._fetchFeedbacks(n)}catch(l){this._showToast(this._t("toast.relabel_failed",{error:l.message||l},"Relabel failed: "+(l.message||l)),"error")}})}}async _histUpload(t,e){const s=await this._ws({type:`${y}/history_import_begin`,entry_id:t}),r=Math.max(4096,s.chunk_bytes||512*1024);let n=0,a=0;for(;aa&&(o=i+1)}await this._ws({type:`${y}/history_import_chunk`,entry_id:t,token:s.token,seq:n,text:e.slice(a,o)}),n+=1,a=o}return s.token}async _histStartScan(t,e,s){e.token=s,e.step="scan",e.error=null,this._render();const r=await this._ws({type:`${y}/start_history_import_scan`,entry_id:t,token:s});e.scanTaskId=r.task_id,this._addProvisionalTask(r.task_id,"history_import",t,0),this._tasksSubscribed||this._pollTaskGeneric(r.task_id),this._histAdopt(r.task_id)}async _histAdopt(t){if(!t)return;let e=(this._tasks||{})[t];if(!e||e.state==="running")try{e=await this._ws({type:`${y}/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:`${y}/get_task_result`,task_id:t.id});e.result=s.result||{},e.accept=new Set((e.result.segments||[]).filter(r=>r.accept).map(r=>r.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. Please scan again."):t.error||this._t("msg.hist_import_failed",{},"Import failed."),e.step="review",this._render();return}try{const r=t.result?t:await this._ws({type:`${y}/get_task_result`,task_id:t.id});e.done=r.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,r,n){if(!(!s||s.type!=="history-import"||!r)){if(t==="hist-scan"){const a=n.getElementById("wd-hist-csv"),o=a?a.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 i=await this._histUpload(r,o);await this._histStartScan(r,s,i)}catch(i){s.error=String(i&&i.message||i),this._render()}});return}if(t==="hist-recorder"){const a=n.getElementById("wd-hist-since"),o=a&&a.value||jt();s.since=o,await this._busyRun("hist-import",async()=>{try{const i=await this._ws({type:`${y}/history_import_recorder`,entry_id:r,start_date:o});if(!i.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(r,s,i.token)}catch(i){s.error=String(i&&i.message||i),this._render()}});return}if(t==="hist-cancel-scan"){if(s.scanTaskId)try{await this._ws({type:`${y}/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 a=s.result&&s.result.segments||[],o=a.every(i=>s.accept.has(i.index));s.accept=o?new Set:new Set(a.map(i=>i.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 a=await this._ws({type:`${y}/apply_history_import`,entry_id:r,scan_task_id:s.scanTaskId,accept:[...s.accept].sort((o,i)=>o-i)});s.applyTaskId=a.task_id,this._addProvisionalTask(a.task_id,"history_import_apply",r,s.accept.size),this._tasksSubscribed||this._pollTaskGeneric(a.task_id),this._histAdopt(a.task_id)}catch(a){s.error=String(a&&a.message||a),this._render()}});return}if(t==="hist-goto-cycles"){this._modal=null,this._tab="history",this._cycleFilter={...this._cycleFilter||{},status:"imported"},this._fetchTabData();return}}}async _onMActImport(t,e,s,r,n){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 a={type:`${y}/store_import_cycle`,entry_id:r,cycle_id:s.cycleId};if(s.mode==="merge"){const o=n.getElementById("wd-store-import-target")?.value||"";if(!o){this._showToast(this._t("toast.store_pick_profile",{},"Pick a profile to merge into"),"error");return}a.target_profile=o}else{const o=(n.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}a.new_profile_name=o}await this._busyRun("store-import",async()=>{try{const o=await this._ws(a);if(o&&o.error){this._showToast(this._t("toast.store_import_failed",{error:o.error},"Import failed: "+o.error),"error");return}this._modal=null,this._showToast(this._t("toast.store_imported",{profile:o&&o.profile||""},`Imported into ${o&&o.profile||"profile"}`)),await this._fetchProfiles(r)}catch(o){this._showToast(this._t("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 a=(n.getElementById("wd-store-share-prog")?.value||"").trim(),o=(n.getElementById("wd-store-share-desc")?.value||"").trim();if(!a){this._showToast(this._t("toast.store_pick_profile",{},"Pick a profile to share into"),"error");return}await this._busyRun("store-share",async()=>{try{const i=await this._ws({type:`${y}/store_upload_cycle`,entry_id:r,local_cycle_id:s.cycleId,program:a,description:o});if(i&&i.error){if(i.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=i.detail?`${i.error} - ${i.detail}`:i.error;this._showToast(this._t("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(i){this._showToast(this._t("toast.store_share_failed",{error:i.message||i},"Share failed: "+(i.message||i)),"error")}});return}if(s&&(s.type==="export-select"||s.type==="import-wizard")){const a=s.type==="export-select"?{categories:s.inventory||{}}:s.manifest||{categories:{}};if(t==="wiz-toggle-all"){const o=a.categories||{},i=s.type==="import-wizard";let l=!0;this._wizCatOrder().filter(c=>o[c]&&o[c].present).forEach(c=>{i&&o[c].importable===!1||this._wizCatState(s,c,a).state!=="all"&&(l=!1)}),s.sel=l?{cats:new Set,profiles:new Set,realIds:new Set,refIds:new Set}:this._wizInitSel(a,i),this._render();return}if(t==="wiz-toggle-cat"){const o=e.dataset.cat,l=this._wizCatState(s,o,a).state!=="all";if(o==="profiles"){const c=a.categories.profiles&&a.categories.profiles.items||[];s.sel.profiles=new Set(l?c.map(p=>p.name):[])}else if(o==="real_cycles"||o==="reference_cycles"){const c=new Set;l&&(a.categories[o]&&a.categories[o].groups||[]).forEach(p=>p.cycles.forEach(_=>{_.id!=null&&c.add(String(_.id))})),o==="real_cycles"?s.sel.realIds=c:s.sel.refIds=c}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,i=e.dataset.prof,l=o==="real_cycles"?s.sel.realIds:s.sel.refIds,c=this._wizGroupIds(a,o,i),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,i=e.dataset.cid,l=o==="real_cycles"?s.sel.realIds:s.sel.refIds;l.has(i)?l.delete(i):l.add(i),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"&&r){const a=n.getElementById("wd-import-json"),o=a?a.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 i=await this._ws({type:`${y}/analyze_import`,entry_id:r,json_data:o});if(!this._isActiveEntry(r)||!this._modal||this._modal.type!=="import-wizard")return;const l=i&&i.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(i){if(!this._isActiveEntry(r)||!this._modal||this._modal.type!=="import-wizard")return;s.step="input",s.error=i&&i.message||String(i),this._render()}return}if(t==="import-apply-ok"&&r){const a=this._wizSelectionPayload(s);await this._busyRun("import-wizard",async()=>{try{const o=await this._ws({type:`${y}/import_config_selective`,entry_id:r,json_data:s.jsonText,selection:a,mode:s.mode,conflict_resolutions:s.conflicts,cycle_destination:s.cycleDest,apply_settings:!0}),i=o&&o.summary||{};this._modal=null,this._showToast(this._t("toast.import_selective_done",{profiles:i.profiles_imported||0,cycles:(i.real_cycles_imported||0)+(i.reference_cycles_imported||0)},`Imported ${i.profiles_imported||0} profile(s) and ${(i.real_cycles_imported||0)+(i.reference_cycles_imported||0)} cycle(s)`)),await this._fetchCycles(r),await this._fetchProfiles(r)}catch(o){this._showToast(this._t("toast.import_failed",{error:o.message||o},"Import failed: "+(o.message||o)),"error")}});return}}}async _onMActStoreShare(t,e,s,r){if(s&&s.type==="store-share-device"){if(t==="sd-toggle-cyc"){const n=e.dataset.cid;s.selected.has(n)?s.selected.delete(n):s.selected.add(n),this._render();return}if(t==="sd-toggle-prof"){const n=e.dataset.prog,a=this._shareableByProgram().find(o=>o.program===n);if(a){const o=a.cycles.every(i=>s.selected.has(i.id));a.cycles.forEach(i=>{o?s.selected.delete(i.id):s.selected.add(i.id)})}this._render();return}if(t==="sd-toggle-phases"){const n=e.dataset.prog;s.includePhases||(s.includePhases=new Set),s.includePhases.has(n)?s.includePhases.delete(n):s.includePhases.add(n),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 n=new Map;(this._shareableCycles||[]).forEach(l=>n.set(l.id,(l.profile_name||"").trim()));const a=Array.from(s.selected).map(l=>({local_cycle_id:l,program:n.get(l)||""})).filter(l=>l.program);if(!a.length){this._showToast(this._t("toast.share_device_none_sel",{},"Select at least one cycle to share"),"error");return}const o=new Set(a.map(l=>l.program)),i=Array.from(s.includePhases||[]).filter(l=>o.has(l));await this._busyRun("store-share-device",async()=>{try{const l=await this._ws({type:`${y}/store_upload_device`,entry_id:r,items:a,include_phases:i,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 g=l.detail?`${l.error} - ${l.detail}`:l.error;this._showToast(this._t("toast.store_share_failed",{error:g},"Share failed: "+g),"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,u=l&&l.created!=null?l.created:c;if(!c){const g=l&&l.errors&&l.errors[0]||l&&l.detail||"upload_failed";this._showToast(this._t("toast.store_share_failed",{error:g},"Share failed: "+g),"error");return}this._modal=null,p?this._showToast(this._t("toast.store_device_shared_partial",{n:c,failed:p},`Shared ${c} cycle(s); ${p} could not be uploaded.`),"info"):_&&!u?this._showToast(this._t("toast.store_device_shared_all_dup",{n:_},`All ${_} cycle(s) were already in the community store.`),"info"):_?this._showToast(this._t("toast.store_device_shared_some_dup",{created:u,dup:_},`Shared ${u} cycle(s); ${_} were already in the store.`)):this._showToast(this._t("toast.store_device_shared",{n:u},`Shared ${u} cycle(s) to the community store - pending review.`))}catch(l){this._showToast(this._t("toast.store_share_failed",{error:l.message||l},"Share failed: "+(l.message||l)),"error")}});return}}}async _onMActCycleDetail(t,e,s,r){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-review-save"){const n=e.cycleId,a=r.getElementById("wd-cyc-rev-quality")?.value||"",o=!!r.getElementById("wd-cyc-rev-golden")?.checked,i=r.getElementById("wd-cyc-rev-notes")?.value||"",l=Array.from(r.querySelectorAll(".wd-cyc-rev-tag")).filter(_=>_.checked).map(_=>_.value),c=r.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:`${y}/set_ml_review`,entry_id:s,cycle_id:n,quality:a,golden:o,tags:l,notes:i}),c!==p&&await this._ws({type:`${y}/label_cycle`,entry_id:s,cycle_id:n,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),this._modal&&this._modal.cycleId===n&&(this._modal.ml=(this._mlById||{})[n]||this._modal.ml)}catch(_){this._showToast(this._t("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 n=e.cycleId;this._modal=null,this._render(),this._deleteCyclesWithUndo(s,[n]);return}if(t==="cyc-auto-split"){const n=parseInt(r.getElementById("wd-split-gap")?.value||"900",10);await this._busyRun("cyc-auto",async()=>{try{const a=await this._ws({type:`${y}/analyze_split`,entry_id:s,cycle_id:e.cycleId,gap_seconds:n});e.split.offsets=(a.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(a){this._showToast(this._t("toast.auto_detect_failed",{error:a.message||a},"Auto-detect failed: "+(a.message||a)),"error")}});return}if(t==="cyc-apply-trim"){const n=e.cycleId,a=e.trim.start,o=e.trim.end,i=e.curve&&e.curve.full_duration_s||0,l=i>0?Math.max(0,Math.round((o-a)/i*100)):100;if(l<50&&!confirm(this._t("msg.trim_destructive_confirm",{pct:l},`This keeps only ${l}% of the cycle and cannot be undone. Continue?`)))return;this._kickAndTrack({type:`${y}/trim_cycle`,entry_id:s,cycle_id:n,start_s:a,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 n=e.cycleId,a=e.split.offsets.slice(),o=e.split.profiles.slice();this._kickAndTrack({type:`${y}/apply_split`,entry_id:s,cycle_id:n,split_offsets:a,segment_profiles:o},"cyc-split-apply",async i=>{this._showToast(this._t("toast.split_complete",{count:(i.new_ids||[]).length},`Split into ${(i.new_ids||[]).length} cycles`)),await this._closeCycleDetail(s),await this._fetchCycles(s),await this._fetchProfiles(s)});return}}}async _onMActProfilePanel(t,e,s,r,n){if(s&&s.type==="profile-panel"){if(t==="pp-phase-add"){const a=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,i=Math.min(o,a);s.phases.push({name:s.catalog[0]||"",start:i,end:Math.min(i+Math.max(60,a*.1),a)}),this._render();return}if(t==="pp-phase-rm"){const a=+(e&&e.dataset.idx||-1);a>=0&&(s.phases.splice(a,1),this._render());return}if(t==="pp-phase-save"){const a=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:`${y}/set_profile_phases`,entry_id:r,profile_name:s.name,phases:a}),this._showToast(this._t("toast.phases_saved",{},"Phases saved"))}catch(o){this._showToast(this._t("msg.toast_save_failed",{error:o.message||o},"Save failed: "+(o.message||o)),"error")}});return}if(t==="pp-cleanup-del"){const a=s.cleanup?Array.from(s.cleanup.selected):[];if(!a.length)return;await this._busyRun("pp-cleanup-del",async()=>{try{for(const i of a)await this._ws({type:`${y}/delete_cycle`,entry_id:r,cycle_id:i});this._showToast(this._t("toast.cycles_deleted",{count:a.length},`Deleted ${a.length} cycle(s)`));const o=await this._ws({type:`${y}/get_profile_cycles`,entry_id:r,profile_name:s.name});this._modal&&(this._modal.cleanup={cycles:o.cycles||[],selected:new Set}),await this._fetchProfiles(r)}catch(o){this._showToast(this._t("msg.toast_delete_failed",{error:o.message||o},"Delete failed: "+(o.message||o)),"error")}});return}if(t==="pp-cleanup-unlabel"){const a=s.cleanup?Array.from(s.cleanup.selected):[];if(!a.length)return;await this._busyRun("pp-cleanup-unlabel",async()=>{try{for(const i of a)await this._ws({type:`${y}/label_cycle`,entry_id:r,cycle_id:i,profile_name:null});this._showToast(this._t("toast.unlabel_done",{count:a.length},`Unlabelled ${a.length} cycle(s)`));const o=await this._ws({type:`${y}/get_profile_cycles`,entry_id:r,profile_name:s.name});this._modal===s&&(s.cleanup={cycles:o.cycles||[],selected:new Set}),await this._fetchProfiles(r),await this._fetchCycles(r),await this._fetchFeedbacks(r)}catch(o){this._showToast(this._t("toast.unlabel_failed",{error:o.message||o},"Unlabel failed: "+(o.message||o)),"error")}});return}if(t==="pp-rename"){const a=n.getElementById("wd-pp-rename")?.value?.trim(),o=parseFloat(n.getElementById("wd-pp-dur")?.value||"0");if(!a){this._showToast(this._t("msg.toast_name_required",{},"Name required"),"error");return}try{await this._ws({type:`${y}/rename_profile`,entry_id:r,profile_name:s.name,new_name:a,manual_duration_min:o>0?o:null}),this._showToast(this._t("toast.profile_renamed",{},"Profile renamed")),s.name=a,await Promise.all([this._fetchProfiles(r),this._fetchProfileGroups(r)]),s.stats=(this._profiles||[]).find(i=>i.name===a)||s.stats,this._render()}catch(i){this._showToast(this._t("toast.rename_failed",{error:i.message||i},"Rename failed: "+(i.message||i)),"error")}return}if(t==="pp-rebuild"){this._kickAndTrack({type:`${y}/rebuild_envelopes`,entry_id:r},"pp-rebuild",async()=>{try{const a=await this._ws({type:`${y}/get_profile_envelope`,entry_id:r,profile_name:s.name});this._modal&&(this._modal.env=a.envelope)}catch{}this._showToast(this._t("toast.envelope_rebuilt",{},"Envelope rebuilt"))});return}if(t==="pp-delete"){this._deleteProfileWithUndo(r,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 r=Ct[s],n=r&&r.type||e.dataset.ftype||"text";if(e.type==="checkbox"){this._pendingSettings[s]=e.checked;return}if(n==="checkboxlist"){this._pendingSettings[s]=this._collectCheckboxlist(e,s);return}if(n==="entitylist"){this._pendingSettings[s]=Array.from(e.querySelectorAll(".wd-pill")).map(a=>a.dataset.val).filter(Boolean);return}if(n==="timerlist"){this._pendingSettings[s]=Array.from(e.querySelectorAll(".wd-timer-row")).map(a=>({offset_minutes:parseFloat(a.querySelector('[data-field="offset_minutes"]').value)||0,message:(a.querySelector('[data-field="message"]').value||"").trim(),auto_pause:a.querySelector('[data-field="auto_pause"]').checked})).filter(a=>a.offset_minutes>0);return}if(n==="number"){const a=String(e.value).trim();if(a===""){r&&r.clearable?this._pendingSettings[s]=null:delete this._pendingSettings[s];return}const o=parseFloat(a);isNaN(o)||(this._pendingSettings[s]=o);return}if(n==="list"){this._pendingSettings[s]=String(e.value).split(",").map(a=>a.trim()).filter(Boolean);return}if(n==="intlist"){this._pendingSettings[s]=le(e.value);return}if(n==="json"){const a=String(e.value).trim();if(!a){this._pendingSettings[s]=[];return}try{this._pendingSettings[s]=JSON.parse(a)}catch{}return}if(n==="entity"||n==="device"){const a=String(e.value).trim();this._pendingSettings[s]=a||null;return}this._pendingSettings[s]=e.value})}_collectCheckboxlist(t,e){const s=Array.from(t.querySelectorAll("[data-choice]")).filter(n=>n.checked).map(n=>n.dataset.choice);if(s.length)return s;const r=Ct[e];return r&&Array.isArray(r.def)&&r.def.length?r.def.slice():s}_conflictKeysForOpts(t,e){const s=Object.assign({},e||{},t),r=this._conflictCtx(),n=new Set;for(const a of Jt)if(a.check(s,r))for(const o of Object.keys(a.fieldErrors(s,r)))n.add(o);return n}_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(Object.assign({},this._opts,this._pendingSettings),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 r=s.dataset.opt;if(s.type==="checkbox"){e[r]=s.checked;return}if(s.dataset.ftype==="checkboxlist"){e[r]=this._collectCheckboxlist(s,r);return}if(s.dataset.ftype==="entity"||s.dataset.ftype==="device"){e[r]=String(s.value).trim()||null;return}const n=parseFloat(s.value);isNaN(n)?s.value!==""&&(e[r]=s.value):e[r]=n}),e}_liveValidateSettings(t){if(!t)return{};const e=this._readSettingsFormValues(t),s=t.getElementById("wd-settings-form");if(!s)return{};const r={};for(const o of this._suggestions||[])o.key!=null&&o.suggested!=null&&(r[o.key]=+o.suggested);const n={},a=this._conflictCtx();for(const o of Jt){if(!o.check(e,a))continue;const i=o.fieldErrors(e,a);for(const[l,c]of Object.entries(i)){const p=r[l],_=p!=null&&!o.check({...e,[l]:p},a)?{...c,suggFix:p}:c;(n[l]=n[l]||[]).push(_)}}return s.querySelectorAll("[data-cerr]").forEach(o=>{const i=o.dataset.cerr,l=n[i],c=s.querySelector(`.wd-field[data-field="${i}"]`);!l||!l.length?(o.hidden=!0,o.innerHTML="",c&&c.classList.remove("wd-has-conflict")):(o.hidden=!1,c&&c.classList.add("wd-has-conflict"),o.innerHTML=l.map(p=>{const _=this._t(p.msgKey,p.msgVars,p.msgFb);let u="";if(p.suggFix!=null){const g=+p.suggFix.toFixed(2);u=`${this._t("conflict.suggestion_resolves",{val:g},`Stage the pending suggestion (${g}) below to fix this`)}`}else if(p.fixVal!=null&&!isNaN(+p.fixVal)){const g=Number.isInteger(p.fixVal)?p.fixVal:+p.fixVal.toFixed(2);u=``}return`
\u26A0 ${d(_)}${u}
`}).join(""))}),n}_cascadeConflictFix(t,e,s){const r=new Set;for(let n=0;n<10;n++){const a=this._liveValidateSettings(t);let o=!1;for(const[i,l]of Object.entries(a)){if(i===s)continue;const c=l.find(_=>_.fixVal!=null&&!isNaN(+_.fixVal)&&+_.fixVal>0);if(!c)continue;const p=e.querySelector(`[data-opt="${i}"]`);p?(p.value=c.fixVal,this._dirtyOptKeys.add(i)):(this._preCascadeOpts==null&&(this._preCascadeOpts=JSON.parse(JSON.stringify(this._opts||{}))),this._opts={...this._opts,[i]:c.fixVal},(this._cascadePending??={})[i]=c.fixVal),r.add(i),o=!0;break}if(!o)break}if(this._liveValidateSettings(t),this._snapshotFormToPending(t),r.size>0){const n=r.size,a=n>1?"s":"";this._showToast(this._t("conflict.cascade_toast",{n},`Other settings adjusted for consistency: ${n}`),"success")}}_changedOptions(t){const e=this._preCascadeOpts||this._opts||{},s={};for(const[r,n]of Object.entries(t))JSON.stringify(n)!==JSON.stringify(e[r])&&(!(r in e)&&(n===""||n===null)||(s[r]=n));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(i=>{const l=i.dataset.opt,c=Ct[l],p=c&&c.type||i.dataset.ftype||"text";if(i.type==="checkbox"){s[l]=i.checked;return}if(p==="checkboxlist"){s[l]=this._collectCheckboxlist(i,l);return}if(p==="entitylist"){s[l]=Array.from(i.querySelectorAll(".wd-pill")).map(u=>u.dataset.val).filter(Boolean);return}if(p==="timerlist"){s[l]=Array.from(i.querySelectorAll(".wd-timer-row")).map(u=>({offset_minutes:parseFloat(u.querySelector('[data-field="offset_minutes"]').value)||0,message:(u.querySelector('[data-field="message"]').value||"").trim(),auto_pause:u.querySelector('[data-field="auto_pause"]').checked})).filter(u=>u.offset_minutes>0);return}const _=i.value;if(p==="number"){const u=String(_).trim();if(u===""){c&&c.clearable?s[l]=null:delete s[l];return}const g=parseFloat(u);isNaN(g)||(s[l]=g);return}if(p==="list"){s[l]=String(_).split(",").map(u=>u.trim()).filter(Boolean);return}if(p==="intlist"){s[l]=le(_);return}if(p==="json"){const u=String(_).trim();if(!u){s[l]=[];return}try{s[l]=JSON.parse(u)}catch{this._invalidJson=l}return}if(p==="entity"||p==="device"){const u=String(_).trim();s[l]=u||null;return}s[l]=_});const r=this._optDefaults||{};for(const i of Object.keys(r))!(i in this._opts)&&i in s&&JSON.stringify(s[i])===JSON.stringify(r[i])&&delete s[i];if(this._invalidJson){this._showToast(this._t("toast.invalid_json",{key:this._invalidJson},`"${this._invalidJson}" is not valid JSON - fix it or clear the field before saving.`),"error");return}const n=this._liveValidateSettings(t),a=new Set(Object.keys(n));if(a.size>0){const i={},l={};for(const[p,_]of Object.entries(s))a.has(p)?l[p]=_:i[p]=_;const c=this._changedOptions(i);this._pendingSettings={...this._pendingSettings,...l},Object.keys(c).length?await this._busyRun("save-settings",async()=>{try{await this._ws({type:`${y}/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._t("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 i=JSON.parse(JSON.stringify(this._preCascadeOpts||this._opts||{}));if(await this._ws({type:`${y}/set_options`,entry_id:e.entry_id,options:o}),this._opts={...this._opts,...o},this._prevOpts=i,this._cascadePending={},this._preCascadeOpts=null,this._pendingSettings={},this._dirtyOptKeys=new Set,this._stagedSuggestions){try{await this._ws({type:`${y}/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(i){this._showToast(this._t("msg.toast_save_failed",{error:i.message||i},"Save failed: "+(i.message||i)),"error")}})}}customElements.get("ha-washdata-panel")||customElements.define("ha-washdata-panel",Ee);