/*! * WashData - Home Assistant integration for appliance cycle monitoring via smart plugs. * Copyright (C) 2026 Lukas Bandura * SPDX-License-Identifier: AGPL-3.0-or-later * * This file is a MINIFIED BUILD. The corresponding readable source ships in the * same directory and is the licensed, preferred form for modification. * This program comes with ABSOLUTELY NO WARRANTY. See the GNU Affero General * Public License for details: . */ const y="ha_washdata",Ht=(()=>{try{return new URL(import.meta.url).searchParams.get("v")||""}catch{return""}})(),ae=2e4,ne=6e3,Rt=25,re=[{key:"show_contributor",labelKey:"lbl.show_contributor",labelFb:"Show contributor names",docKey:"setting.show_contributor.doc",docFb:'Show the "by " attribution on community appliances and reference cycles.'}],Bt=34,et=["#e6194B","#3cb44b","#4363d8","#f58231","#911eb4","#42d4f4","#f032e6","#bfef45","#fabed4","#469990","#dcbeff","#9A6324","#800000","#aaffc3","#808000","#ffd8b1","#000075","#a9a9a9"],$t="{device}, {duration}, {minutes}, {program}, {energy_kwh}, {cost}, {time_finished}, {vs_typical}, {cycle_count}",mt=[{id:"basic",label:"Basic",intro:"Core identity and the essentials most setups need.",groups:[{sub:"Device info",fields:[{key:"name",label:"Device Name",type:"text",doc:"Display name shown in the HA integrations list and device registry."},{key:"device_type",label:"Device Type",type:"devicetype",doc:"Appliance class. Sets sensible detection defaults (thresholds, off-delay, end handling) tuned for that appliance type; change it only if the device was originally set up as the wrong type."},{key:"store_brand",label:"Appliance Brand",type:"storebrand",optional:!0,doc:"Optional. The appliance brand, picked from the community catalog. Used to find and share matching reference recordings. Leave blank if you are not using online features."},{key:"store_model",label:"Appliance Model",type:"storemodel",optional:!0,doc:"Optional. The appliance model, picked from the community catalog once a brand is set. If your model is not listed you can add it to the catalog."}]},{sub:"Basic configuration",fields:[{key:"power_sensor",label:"Power Sensor",type:"entity",domain:"sensor",doc:"The sensor entity reporting live power in watts for this appliance (e.g. sensor.washer_power). All cycle detection is based on this signal."},{key:"min_power",label:"Minimum Power",unit:"W",type:"number",step:.1,min:0,def:2,basic:!0,doc:"Absolute minimum power considered active. Readings below this are treated as 0 W (standby), filtering out the phantom load of smart plugs and standby LEDs."},{key:"off_delay",label:"Off Delay",unit:"s",type:"number",min:0,def:180,basic:!0,doc:"Time to wait after power drops before declaring the cycle finished. If power resumes within this window the cycle continues seamlessly - this bridges pauses between wash stages. Dishwashers have long drying phases (power off for 20-60 min) so the off-delay must exceed that to keep the whole wash+dry as one cycle."},{key:"linked_device",label:"Group Under Device",type:"device",doc:'Optionally nest this WashData device under another device (e.g. the smart plug) in the HA device registry, shown as "Connected via ...".'}]}]},{id:"detection",label:"Detection",intro:"How a cycle is detected as starting, running and finishing.",groups:[{sub:"Thresholds & Gap",fields:[{key:"start_threshold_w",label:"Start Threshold",unit:"W",type:"number",step:1,min:0,basic:!0,doc:"Power must rise above this level to confirm a cycle has started. Setting it too low causes false starts from standby power; too high and slow-starting programs (cold fill) are missed. The suggestion engine sets this just above the machine's observed lowest active power."},{key:"stop_threshold_w",label:"Stop Threshold",unit:"W",type:"number",step:.1,min:0,basic:!0,doc:"Power must fall below this level before the off-delay countdown begins. Set it below the Start Threshold - the gap between them is the hysteresis band that prevents flicker. If set too high, low-power phases (rinse holds, anti-crease) falsely trigger the end sequence."},{key:"min_off_gap",label:"Min Off Gap",unit:"s",type:"number",min:0,basic:!0,doc:"If the machine powers off for less than this time, the on/off/on sequence is treated as one continuous cycle. Prevents soak programs (machine powers off for several minutes mid-wash) from being split into two separate cycles. Set it shorter than the gap between your back-to-back loads if you want those counted as separate cycles. Device-type defaults protect the typical intra-cycle pause for each appliance."}]},{sub:"Cycle Start",fields:[{key:"start_duration_threshold",label:"Start Duration",unit:"s",type:"number",min:0,def:5,doc:"Power must stay above the start threshold this long to confirm a real start, preventing split-second on/off toggles from starting a cycle."},{key:"start_energy_threshold",label:"Start Energy",unit:"Wh",type:"number",step:.01,min:0,def:.2,doc:"Energy (power x time) the appliance must consume before RUNNING. A brief high-power spike has very low energy and is ignored, preventing false starts."},{key:"completion_min_seconds",label:"Min Cycle Duration",unit:"s",type:"number",min:0,def:600,basic:!0,doc:"Cycles shorter than this are discarded as ghost cycles (test runs, opening the door to add a sock)."}]},{sub:"Cycle End",fields:[{key:"end_energy_threshold",label:"End Energy",unit:"Wh",type:"number",step:.001,min:0,def:.05,doc:"During the off-delay countdown, accumulated energy (watts x time) is compared to this threshold. If exceeded, the countdown resets - keeping anti-crease tumbles and dishwasher drying tails attached to the cycle instead of cutting them short. Raise it if cycles end too early during cool-down; lower it if detection is sluggish."},{key:"end_repeat_count",label:"End Repeat Count",type:"number",min:1,def:1,doc:"Number of consecutive below-stop-threshold readings required before the cycle ends. 1 is fine for most plugs. Raise to 2-3 if your smart plug occasionally reports a false-zero sample mid-cycle and your cycles are ending prematurely."},{key:"smart_termination_duration_ratio",label:"Smart Termination Ratio",type:"number",step:.01,min:.5,max:1,doc:"How far into the matched program's expected duration a cycle must be before Smart Termination may end it early once power drops. The expected duration is the program's average, so on appliances whose runtime varies a lot - washers on cold winter vs warm summer inlet water, sensor-dry dryers, load-dependent programs - about half of all runs finish shorter than that average and never get the fast finish, ending only via the fallback timeout minutes late. Lower this (e.g. 0.85) on those machines so the early finish still fires; raise it toward 1.0 to be more conservative. Leave empty for the default (0.98, or 0.99 for dishwashers). It can only ever end a cycle earlier, never later, and never fires on an ambiguous or low-confidence match."}]},{sub:"Power Off",fields:[{key:"power_off_threshold_w",label:"Power Off Threshold",unit:"W",type:"number",step:.1,min:0,def:0,doc:"Optional power-based Off detection. When above 0, once a cycle has finished and power stays below this level for the Power Off Delay, the machine is treated as switched off and the state returns to Off. Leave at 0 to disable (the default). Set it above the true switched-off floor and below the Stop Threshold and your machine's finished-but-on standby draw; if it is not below the Stop Threshold it is ignored. When enabled it replaces the Progress Reset Delay for returning to Off, so a finished machine stays in Finished/Clean until it is actually powered off."},{key:"power_off_delay",label:"Power Off Delay",unit:"s",type:"number",min:0,def:30,doc:"How long power must stay below the Power Off Threshold after a cycle finishes before the state returns to Off. Only used when the Power Off Threshold is above 0. Checked on the background cadence, so the effective delay rounds up to the next state-expiry tick."}]},{sub:"Signal Processing",fields:[{key:"sampling_interval",label:"Sampling Interval",unit:"s",type:"number",min:1,def:30,doc:"Expected time between sensor readings - used to size the smoothing window and start debounce correctly. Every sensor update is captured regardless of this value; it only calibrates the downstream calculations. The suggestion engine measures your sensor's actual cadence from past cycles and sets this automatically."},{key:"smoothing_window",label:"Smoothing Window",type:"number",min:1,def:2,doc:"How much the raw power signal is smoothed. Low (2) is responsive but noisy; high (5) smooths spikes but adds lag."}]}]},{id:"matching",label:"Matching",intro:"How finished cycles are matched to learned profiles and labelled.",notDeviceTypes:["other"],groups:[{sub:"Match Scoring",fields:[{key:"profile_match_threshold",label:"Match Threshold",type:"number",step:.01,min:0,max:1,def:.4,doc:"Minimum similarity score (0-1) required at cycle end to accept a program identification. Raise it to reduce wrong identifications; lower it if your machine's programs are not being matched. Default 0.4 is a conservative starting point."},{key:"profile_unmatch_threshold",label:"Unmatch Threshold",type:"number",step:.01,min:0,max:1,def:.35,doc:"If a live mid-cycle match drops below this score, the tentative identification is cleared. Keep it a little below the Match Threshold so a brief dip in similarity does not flip the display back to unmatched."},{key:"profile_match_interval",label:"Match Interval",unit:"s",type:"number",min:0,doc:"How often to attempt profile matching during a running cycle. Default 300 s (5 minutes) balances detection speed and CPU."}]},{sub:"Duration Gates",fields:[{key:"profile_match_min_duration_ratio",label:"Min Duration Ratio",type:"number",step:.01,min:0,max:1,def:.1,doc:"Minimum cycle length relative to the profile. 0.9 means a cycle must be at least 90% of the profile duration to match."},{key:"profile_match_max_duration_ratio",label:"Max Duration Ratio",type:"number",step:.01,min:0,def:1.5,doc:"Maximum cycle length relative to the profile. 1.5 means a cycle must be under 150% of the profile duration to match."},{key:"profile_duration_tolerance",label:"Profile Duration Tolerance",type:"number",step:.01,min:0,max:1,def:.25,doc:"The +/- band around a profile average duration used during matching. 0.25 means a 60 min profile matches 45-75 min cycles."},{key:"duration_tolerance",label:"Estimate Tolerance",type:"number",step:.01,min:0,max:1,def:.1,doc:"Tolerance for time-remaining estimates (learning feedback, not matching). If the actual duration is within +/-X% of the estimate it counts as a good match."}]},{sub:"Profile Evidence",fields:[{key:"profile_evidence_sources",label:"Cycles that shape a program",type:"checkboxlist",def:["real_cycles","reference_cycles","backfill_cycles"],choices:[["real_cycles","Cycles this machine ran"],["reference_cycles","Downloaded from the community store"],["backfill_cycles","Found in imported power history"]],doc:"Which cycles are used to build each program's power curve, and to match a finished cycle against it. Unticking a kind stops it shaping your programs without deleting anything - the cycles stay in your Cycles list and can still be labelled or removed. Useful if you do not trust imported data. Statistics are unaffected: they always count only the cycles this machine actually ran. Unticking everything is ignored, since a program with no cycles behind it could never match."}]},{sub:"Auto-Labeling",fields:[{key:"auto_label_confidence",label:"Auto-Label Confidence",type:"number",step:.01,min:0,max:1,def:.9,doc:"If the match score at cycle end is at or above this, the program is labeled automatically without any confirmation prompt. Raise it to require higher certainty before auto-labeling; lower it to automate more. Works in conjunction with Learning Confidence below it."},{key:"learning_confidence",label:"Learning Confidence",type:"number",step:.01,min:0,max:1,def:.6,doc:"If the match score falls between this and Auto-Label Confidence, WashData flags the finished cycle for review in the Cycles queue so you can verify the identified program. Below this score the match is too uncertain to surface. Must be kept below Auto-Label Confidence."}]}]},{id:"phase_eta",label:"Time Remaining",intro:"Phase-aware time-remaining, for machines whose cycle length depends on temperature or spin.",onlyDeviceTypes:["washing_machine","washer_dryer"],fields:[{key:"enable_phase_matching",label:"Phase-aware time remaining",type:"checkbox",def:!1,doc:"Break each running cycle into phases (heating, wash, spin) and budget the time remaining per phase, blended with the classic estimate - leaning on the phase budget early in the cycle and the classic estimate near the end. This personalises the countdown to how long your machine actually heats and runs, which is most noticeable in the first half of a cycle. Off = the classic estimate only. Only the time-remaining display is affected; program matching and cycle detection are unchanged."}]},{id:"timing",label:"Timing & Watchdog",intro:"Background cadence, the offline watchdog and housekeeping.",groups:[{sub:"Watchdog",fields:[{key:"watchdog_interval",label:"Watchdog Interval",unit:"s",type:"number",min:1,def:30,doc:"How often the background watchdog checks for stalled sensors and elapsed timeouts. Default 30 s."},{key:"no_update_active_timeout",label:"No-Update Timeout",unit:"s",type:"number",min:0,def:600,doc:"If no power updates arrive for this long while running, assume the plug dropped offline and force-stop to avoid a zombie cycle. Default 600 s allows for cloud or mesh lag."}]},{sub:"Housekeeping",fields:[{key:"progress_reset_delay",label:"Progress Reset Delay",unit:"s",type:"number",min:0,def:1800,doc:"After finishing, hold progress at 100% for this long so Completed is visible on dashboards before resetting to Idle."},{key:"auto_maintenance",label:"Auto Maintenance (nightly cleanup)",type:"checkbox",def:!0,doc:"Run nightly housekeeping: rebuild profile envelopes, recompute cycle health, prune debug traces and retain the most recent cycles."},{key:"power_profile_interval_min",label:"Power Profile Interval",unit:"min",type:"number",min:1,def:15,doc:"Bucket size for the per-profile power_profile sensor attribute (the flat per-slot average-watts array consumed by external planners such as EMHASS and tibber_prices). Smaller buckets keep short power spikes sharp; larger buckets smooth the shape. Default 15 min. Read-time only; does not affect detection."}]},{sub:"Debug",fields:[{key:"expose_debug_entities",label:"Expose Debug Entities",type:"checkbox",doc:"Publish extra diagnostic HA entities (match confidence, ambiguity, state internals). Off keeps the entity list clean for normal use."},{key:"save_debug_traces",label:"Save Debug Traces",type:"checkbox",doc:"Store the full power trace and matching debug data for each cycle. Useful for troubleshooting but increases storage size."}]}]},{id:"anti_wrinkle",label:"Anti-Wrinkle",intro:"Anti-wrinkle / anti-crease mode detects low-power tumble pulses after the main phase and keeps them attached to the finished cycle instead of reading them as new cycles.",onlyDeviceTypes:["washing_machine","dryer","washer_dryer"],fields:[{key:"anti_wrinkle_enabled",label:"Enable Anti-Wrinkle Detection",type:"checkbox",doc:"Recognise the short low-power tumble pulses a dryer emits after the main heat phase and keep them attached to the finished cycle instead of reading them as new cycles."},{key:"anti_wrinkle_max_power",label:"Max Anti-Wrinkle Power",unit:"W",type:"number",step:10,min:0,def:400,doc:"A pulse above this power is treated as a real new cycle, not an anti-wrinkle tumble. Set just above the tumble-pulse power."},{key:"anti_wrinkle_max_duration",label:"Max Duration",unit:"s",type:"number",min:0,def:60,doc:"Pulses longer than this are treated as a real cycle rather than an anti-wrinkle tumble."},{key:"anti_wrinkle_exit_power",label:"Exit Power Threshold",unit:"W",type:"number",step:.1,min:0,def:.8,doc:"Power must fall below this between pulses for anti-wrinkle mode to stay active."},{key:"anti_wrinkle_idle_timeout",label:"Max Pulse Gap",unit:"s",type:"number",step:30,min:0,def:120,doc:"How long the machine may stay quiet between two tumble pulses before anti-wrinkle mode ends. Set it above the longest gap your dryer leaves between pulses, otherwise every later pulse is read as a false start."}]},{id:"dishwasher",label:"Dishwasher",intro:"End-of-cycle handling for dishwashers, which typically finish with a long near-silent drying phase before a short final drain.",onlyDeviceTypes:["dishwasher"],fields:[{key:"dishwasher_end_spike_quiet_release",label:"Passive-Dry Quiet Release",unit:"s",type:"number",step:60,min:0,def:600,doc:"Once the cycle passes its expected duration, how long the dishwasher must stay quiet (below the Stop Threshold) before WashData stops waiting for a final drain and ends the cycle. Raise it if your machine has a long silent drying phase before a late final drain that is being missed - a wider window lets the learned duration follow seasonal drift (colder inlet water = longer cycles) instead of locking to the old average. It only ever shortens the wait relative to the internal 30-minute end-spike cap, never extends it."}]},{id:"delay",label:"Delay Start",intro:"Delayed-start detection identifies when an appliance is powered but has not yet begun its cycle.",fields:[{key:"delay_start_detect_enabled",label:"Enable Delay-Start Detection",type:"checkbox",doc:"Detect when the appliance is powered on and waiting (delayed start / standby) but has not begun its cycle, so standby draw is not mistaken for a running cycle."},{key:"delay_confirm_seconds",label:"Confirm Seconds",unit:"s",type:"number",min:0,def:60,doc:"Power must stay in the standby band for this long before the appliance is treated as waiting-to-start rather than running."},{key:"delay_timeout_hours",label:"Timeout Hours",unit:"h",type:"number",step:.5,min:0,def:8,doc:"Stop waiting in delayed-start mode after this many hours and return to idle, so a machine left powered but never started does not wait forever."}]},{id:"triggers",label:"Triggers & Door",intro:"Optional external signals: an end trigger, a door sensor, a pause switch, and the unload reminder.",groups:[{sub:"External End Trigger",fields:[{key:"external_end_trigger_enabled",label:"Enable External End Trigger",type:"checkbox",doc:"Let an external binary sensor signal the end of a cycle, in addition to the built-in power-based detection."},{key:"external_end_trigger",label:"External Trigger Entity",type:"entity",domain:"binary_sensor",doc:'Binary sensor whose state change marks the cycle end (e.g. an appliance "finished" contact or a companion integration).'},{key:"external_end_trigger_inverted",label:"Invert External Trigger (trigger on OFF)",type:"checkbox",doc:"Treat the trigger sensor turning OFF (rather than ON) as the end-of-cycle signal."}]},{sub:"Door & Pause",fields:[{key:"door_sensor_entity",label:"Door Sensor Entity",type:"entity",domain:"binary_sensor",doc:"Optional door binary sensor. Used to detect when the appliance has been opened/unloaded after a cycle."},{key:"door_opens_at_end",label:"Door Opens Automatically At End",type:"checkbox",doc:"For dishwashers that pop the door open at the end of the cycle to dry (AirDry and similar). With this on, a door-open on a running cycle no longer pauses it forever; instead, if the door stays open for the dwell below, WashData treats the cycle as finished. A brief open (adding an item) is ignored. Requires a Door Sensor Entity."},{key:"door_end_dwell_seconds",label:"Door-Open End Dwell",unit:"s",type:"number",min:1,def:60,doc:'How long the door must stay open before WashData ends the cycle, when "Door Opens Automatically At End" is on. Long enough to ignore quickly adding a dish (default 60 s), short enough to end promptly once the machine pops the door.'},{key:"pause_cuts_power",label:"Pause Also Cuts Power (via switch)",type:"checkbox",doc:"When a cycle is paused, also switch off the Switch Entity below. Only for appliances whose plug can safely be cut mid-cycle."},{key:"switch_entity",label:"Switch Entity",type:"entity",domain:"switch",doc:'Optional switch toggled off on pause and back on when resuming, used together with "Pause also cuts power".'}]},{sub:"Unload Reminder",fields:[{key:"notify_unload_delay_minutes",label:"Unload Nag Delay",unit:"min",type:"number",min:0,def:60,basic:!0,doc:'Minutes after a cycle ends before sending the still-waiting "unload the machine" reminder. Set 0 to disable the reminder.'},{key:"notify_unload_repeat",label:"Repeat Until Door Opens",type:"checkbox",doc:'Keep re-sending the unload reminder every "Unload Nag Delay" minutes until you open the door or tap "Stop reminding" on the notification. Requires a Door Sensor Entity (the reminder itself does). The dismiss button works on Home Assistant companion-app (mobile) notifications.'},{key:"pump_stuck_duration",label:"Pump Stuck Duration",unit:"s",type:"number",min:0,def:1800,onlyDeviceType:"pump",doc:"Seconds a pump may run continuously before it is flagged as possibly stuck (fires the stuck-pump event)."}]}]},{id:"notifications",label:"Notifications",groups:[{sub:"Services",fields:[{key:"notify_start_services",label:"Start Services",type:"entitylist",domain:"notify",placeholder:"add a notify service\u2026",basic:!0,doc:"notify.* services called when a cycle starts. Add one per target (phone, dashboard, etc.); leave empty for no start notification."},{key:"notify_finish_services",label:"Finish Services",type:"entitylist",domain:"notify",placeholder:"add a notify service\u2026",basic:!0,doc:"notify.* services called when a cycle finishes. Add one per target; leave empty for no finish notification."},{key:"notify_live_services",label:"Live Progress Services",type:"entitylist",domain:"notify",placeholder:"add a notify service\u2026",doc:"notify.* services called for live progress updates while a cycle runs. Leave empty to disable live-progress notifications."},{key:"notify_people",label:"People (for Only When Home)",type:"entitylist",domain:"person",placeholder:"add a person\u2026",doc:'person.* entities used by "Notify Only When Home" to decide whether anyone is home.'},{key:"notify_only_when_home",label:"Notify Only When Home",type:"checkbox",doc:"Only send notifications when at least one of the linked people (above) is home."},{key:"notify_fire_events",label:"Fire HA Events for Notifications",type:"checkbox",def:!0,doc:"Also fire ha_washdata_* events on cycle start/finish so you can build your own automations."}]},{sub:"Timing",fields:[{key:"notify_before_end_minutes",label:"Pre-End Alert",unit:"min",type:"number",min:0,def:0,doc:"Send an Almost Done alert when estimated time remaining drops below this. 0 disables it."},{key:"notify_live_interval_seconds",label:"Live Update Interval",unit:"s",type:"number",min:30,def:300,doc:"How often live-progress notifications are refreshed while a cycle runs."},{key:"notify_live_overrun_percent",label:"Live Overrun % Before Alert",unit:"%",type:"number",min:0,def:20,doc:"If a cycle runs past its estimate by more than this percentage, send an overrun alert."},{key:"notify_live_chronometer",label:"Use Live Chronometer",type:"checkbox",doc:"Show a live-updating countdown timer in the notification (on platforms that support it) instead of a static estimate."},{key:"notify_live_sticky",label:"Keep Live Notification On Tap",type:"checkbox",doc:"Android only. Make the live-progress notification persistent (sticky) so tapping it does not dismiss the ongoing thread. Off keeps the default behaviour where a tap dismisses it."},{key:"notify_live_click_action",label:"Live Notification Tap Target",type:"text",optional:!0,doc:"Android only. Where a tap on the live-progress notification opens (e.g. /lovelace/laundry, or a full URL) instead of the app landing page. Leave blank for the default."},{key:"notify_timeout_seconds",label:"Auto-Dismiss After",unit:"s",type:"number",min:0,def:0,doc:"Automatically dismiss the notification after this many seconds (on platforms that support it). 0 keeps it until dismissed manually."}]},{sub:"Messages",fields:[{key:"notify_title",label:"Notification Title",type:"text",def:"WashData: {device}",doc:`Notification title. Template variables: ${$t}.`},{key:"notify_icon",label:"Notification Icon",type:"text",def:"",doc:"Optional mdi icon for the notification (e.g. mdi:washing-machine). Leave blank for the platform default."},{key:"notify_start_message",label:"Start Message",type:"textarea",def:"{device} started.",doc:`Body sent when a cycle starts. Template variables: ${$t}.`},{key:"notify_finish_message",label:"Finish Message",type:"textarea",def:"{device} finished. Duration: {duration}m.",basic:!0,doc:`Body sent when a cycle finishes. Template variables: ${$t}. {time_finished} and {vs_typical} are most useful here.`},{key:"notify_pre_complete_message",label:"Pre-Complete Message",type:"textarea",def:"{device}: Less than {minutes} minutes remaining.",doc:`Body of the pre-end / almost-done alert. Template variables: ${$t}.`},{key:"notify_reminder_message",label:"Reminder Message",type:"textarea",def:"",doc:`Body of the still-waiting unload reminder. Blank uses the built-in default. Template variables: ${$t}.`},{key:"notify_channel",label:"Android Channel (start/live)",type:"text",def:"",placeholder:"e.g. WashData",suggestions:["WashData","WashData Status","Appliance Status"],doc:"Android notification channel name for start/live messages (controls per-channel sound and priority on the mobile app). Blank uses the companion app default."},{key:"notify_finish_channel",label:"Android Channel (finish)",type:"text",def:"",placeholder:"e.g. WashData Finished",suggestions:["WashData Finished","WashData Alerts","Appliance Finished"],doc:"Android notification channel name for the finish message. Blank reuses the start/live channel."}]},{sub:"Energy",fields:[{key:"energy_sensor",label:"Energy Meter Entity",type:"entity",domain:"sensor",optional:!0,doc:"Optional cumulative energy counter (total_increasing kWh/Wh, e.g. the plug's own lifetime meter). When set, each cycle's reported energy is taken from this counter's start-to-end delta, which avoids the under-counting you get from integrating a slow-reporting power sensor. Falls back to the integrated value if the reading is missing, its unit is unknown, or the delta is not positive. Leave blank to keep integrating the power sensor."},{key:"energy_price_entity",label:"Energy Price Entity",type:"entity",domain:"sensor",basic:!0,doc:"Sensor with the current electricity price per kWh (e.g. a dynamic tariff). Takes precedence over the static price below. Each cycle freezes the price in effect when it finished."},{key:"energy_price_static",label:"Static Energy Price (per kWh)",type:"number",step:.001,min:0,basic:!0,doc:"Fixed price per kWh used for cost figures when no live price entity is set above."},{key:"peak_rate_threshold",label:"Peak-Rate Threshold (per kWh)",type:"number",step:.001,min:0,def:0,clearable:!0,doc:"When a cycle starts and the current price per kWh is at or above this value, append a peak-rate tip to the start notification. 0 or blank disables the tip."},{key:"peak_rate_message",label:"Peak-Rate Message",type:"text",def:"",placeholder:"Running at peak rate ({price}/kWh).",doc:"Optional custom text for the peak-rate tip appended to the start notification. Template variables: {device}, {price}. Blank uses the built-in default."}]},{sub:"Cycle Timers",fields:[{key:"notify_cycle_timers",label:"Cycle Timers",type:"timerlist",doc:"Notifications at specific minutes into a cycle (e.g. to add softener). Message supports {device}, {program}, {minutes}. Enable Auto-pause to pause at that point and receive an interactive notification with a Resume button; resume via the panel, the pause/resume service, or the notification action."}]},{sub:"Quiet Hours & Milestones",fields:[{key:"notify_quiet_start_hour",label:"Quiet Hours Start",unit:"h",type:"number",min:0,max:23,clearable:!0,doc:"Start of a do-not-disturb window (0-23). Finish, reminder and clean-laundry notifications that would fire during quiet hours are held and delivered when the window ends. Leave blank to disable. Supports windows that cross midnight (e.g. start 22, end 7)."},{key:"notify_quiet_end_hour",label:"Quiet Hours End",unit:"h",type:"number",min:0,max:23,clearable:!0,doc:"End of the do-not-disturb window (0-23). Held notifications are delivered at this hour. Leave blank to disable."},{key:"notify_milestones",label:"Cycle Milestones",type:"intlist",def:"50, 100, 500, 1000",placeholder:"50, 100, 500, 1000",doc:"Comma-separated cycle counts that trigger a one-off celebration notification when reached (e.g. 50, 100, 500, 1000). Blank disables milestone notifications."},{key:"notify_milestone_message",label:"Milestone Message",type:"textarea",def:"{device} has completed {cycle_count} cycles!",doc:"Message for the milestone notification. Template variables: {device}, {cycle_count}."}]}]},{id:"ml_training",label:"ML Training",fields:[{key:"enable_ml_models",label:"Apply smart models during a cycle",type:"checkbox",def:!1,doc:"While a cycle runs, let the models refine the live results: a steadier time-remaining and energy/cost estimate, and an anti-premature-stop guard on end detection (it can only ever delay a finish, never end one early, and is bounded). Uses your fine-tuned models when available, otherwise the built-in ones. Off = the classic power-based logic only (still reliable)."},{key:"ml_training_enabled",label:"Learn from this machine",type:"checkbox",def:!1,doc:"Periodically study your reviewed cycles overnight and fine-tune the models to this specific machine. A change is only kept when it genuinely scores better on held-out cycles, so this can only help or stay the same \u2014 never regress."},{key:"ml_training_hour",label:"Learn at hour",unit:"h",type:"number",min:0,max:23,def:2,doc:"Local hour of day (0-23) to do the overnight fine-tuning. Pick a quiet hour such as 2 (02:00)."},{key:"ml_training_min_cycles",label:"Cycles needed first",type:"number",min:5,def:30,doc:"Wait until at least this many cycles have been recorded before fine-tuning, so there is enough to learn from."},{key:"ml_training_interval_days",label:"Check at most every",unit:"days",type:"number",min:1,def:7,doc:"Re-check for improvements at most once per this many days."}]}],kt={};for(const h of mt){const t=h.groups||[{fields:h.fields}];for(const e of t)for(const s of e.fields||[])kt[s.key]=s}const qt={profile_match_min_duration_ratio:.1,profile_match_max_duration_ratio:1.5,corr_weight:.45,keep_min_score:.1,dtw_bandwidth:.2,dtw_blend:.5,dtw_ensemble_w:.7,dtw_ddtw_scale:30,dtw_refine_top_n:5,duration_weight:.22,energy_weight:.22,duration_scale:.175,energy_scale:.25},Vt=[{keys:["start_threshold_w","stop_threshold_w"],check:h=>h.start_threshold_w!=null&&h.stop_threshold_w!=null&&h.start_threshold_w<=h.stop_threshold_w,fieldErrors:h=>({start_threshold_w:{msgKey:"conflict.hysteresis.start",msgVars:{stop:h.stop_threshold_w},msgFb:`Must be above Stop Threshold (${h.stop_threshold_w} W)`,fixVal:+Math.max(h.stop_threshold_w+.5,h.stop_threshold_w*1.25).toFixed(1)},stop_threshold_w:{msgKey:"conflict.hysteresis.stop",msgVars:{start:h.start_threshold_w},msgFb:`Must be below Start Threshold (${h.start_threshold_w} W)`,fixVal:+Math.min(h.start_threshold_w-.5,h.start_threshold_w*.8).toFixed(1)}})},{keys:["min_power","stop_threshold_w"],check:h=>h.min_power!=null&&h.stop_threshold_w!=null&&h.min_power>h.stop_threshold_w,fieldErrors:h=>({min_power:{msgKey:"conflict.min_power.min_power",msgVars:{stop:h.stop_threshold_w},msgFb:`Must be at or below Stop Threshold (${h.stop_threshold_w} W)`,fixVal:+(h.stop_threshold_w*.8).toFixed(1)},stop_threshold_w:{msgKey:"conflict.min_power.stop",msgVars:{min:h.min_power},msgFb:`Must be at or above Min Power (${h.min_power} W)`,fixVal:+(h.min_power*1.25).toFixed(1)}})},{keys:["power_off_threshold_w","stop_threshold_w"],check:h=>h.power_off_threshold_w!=null&&h.power_off_threshold_w>0&&h.stop_threshold_w!=null&&h.power_off_threshold_w>=h.stop_threshold_w,fieldErrors:h=>({power_off_threshold_w:{msgKey:"conflict.power_off.threshold",msgVars:{stop:h.stop_threshold_w},msgFb:`Must be below Stop Threshold (${h.stop_threshold_w} W) to take effect`,fixVal:+(h.stop_threshold_w*.6).toFixed(1)},stop_threshold_w:{msgKey:"conflict.power_off.stop",msgVars:{pot:h.power_off_threshold_w},msgFb:`Must be above Power Off Threshold (${h.power_off_threshold_w} W)`,fixVal:+(h.power_off_threshold_w*1.67).toFixed(1)}})},{keys:["off_delay","min_off_gap"],check:h=>h.off_delay!=null&&h.min_off_gap!=null&&h.off_delay>h.min_off_gap,fieldErrors:h=>({off_delay:{msgKey:"conflict.off_delay.off_delay",msgVars:{gap:h.min_off_gap},msgFb:`Off Delay (${h.off_delay} s) overrides Min Off Gap (${h.min_off_gap} s); cycles within the gap may merge`,fixVal:h.min_off_gap},min_off_gap:{msgKey:"conflict.off_delay.gap",msgVars:{delay:h.off_delay},msgFb:`Min Off Gap should be at least Off Delay (${h.off_delay} s)`,fixVal:h.off_delay}})},{keys:["watchdog_interval","sampling_interval"],check:h=>h.watchdog_interval!=null&&h.sampling_interval!=null&&h.watchdog_interval<2*h.sampling_interval,fieldErrors:h=>({watchdog_interval:{msgKey:"conflict.watchdog.interval",msgVars:{si:h.sampling_interval},msgFb:`Should be at least 2\xD7 Sampling Interval (${h.sampling_interval} s)`,fixVal:+(2*h.sampling_interval+1)},sampling_interval:{msgKey:"conflict.watchdog.sampling",msgVars:{wi:h.watchdog_interval},msgFb:`Sampling Interval should be at most half of Watchdog Interval (${h.watchdog_interval} s)`,fixVal:+Math.floor(h.watchdog_interval/2)}})},{keys:["no_update_active_timeout","watchdog_interval"],check:h=>h.no_update_active_timeout!=null&&h.watchdog_interval!=null&&h.no_update_active_timeout<=h.watchdog_interval,fieldErrors:h=>({no_update_active_timeout:{msgKey:"conflict.no_update_timeout.timeout",msgVars:{wi:h.watchdog_interval},msgFb:`Must be greater than Watchdog Interval (${h.watchdog_interval} s)`,fixVal:h.watchdog_interval*2},watchdog_interval:{msgKey:"conflict.no_update_timeout.watchdog",msgVars:{to:h.no_update_active_timeout},msgFb:`Must be less than No-Update Timeout (${h.no_update_active_timeout} s)`,fixVal:+Math.floor(h.no_update_active_timeout/2)}})},{keys:["start_duration_threshold","sampling_interval"],check:h=>h.start_duration_threshold!=null&&h.sampling_interval!=null&&h.start_duration_threshold({start_duration_threshold:{msgKey:"conflict.start_dur.threshold",msgVars:{si:h.sampling_interval},msgFb:`Should be at least one Sampling Interval (${h.sampling_interval} s) to prevent single-sample false starts`,fixVal:h.sampling_interval},sampling_interval:{msgKey:"conflict.start_dur.sampling",msgVars:{sdt:h.start_duration_threshold},msgFb:`Sampling Interval exceeds Start Duration (${h.start_duration_threshold} s); single-sample spikes can open a cycle`,fixVal:h.start_duration_threshold}})},{keys:["learning_confidence","profile_match_threshold"],check:h=>h.learning_confidence!=null&&h.profile_match_threshold!=null&&h.learning_confidence({learning_confidence:{msgKey:"conflict.confidence.learning",msgVars:{match:h.profile_match_threshold},msgFb:`Must be at or above Match Threshold (${h.profile_match_threshold})`,fixVal:+h.profile_match_threshold.toFixed(2)},profile_match_threshold:{msgKey:"conflict.confidence.match_for_learning",msgVars:{lc:h.learning_confidence},msgFb:`Must be at or below Learning Confidence (${h.learning_confidence})`,fixVal:+h.learning_confidence.toFixed(2)}})},{keys:["profile_match_threshold","auto_label_confidence"],check:h=>h.profile_match_threshold!=null&&h.auto_label_confidence!=null&&h.profile_match_threshold>h.auto_label_confidence,fieldErrors:h=>({profile_match_threshold:{msgKey:"conflict.confidence.match_for_auto",msgVars:{alc:h.auto_label_confidence},msgFb:`Must be at or below Auto-Label Confidence (${h.auto_label_confidence})`,fixVal:+h.auto_label_confidence.toFixed(2)},auto_label_confidence:{msgKey:"conflict.confidence.auto",msgVars:{match:h.profile_match_threshold},msgFb:`Must be at or above Match Threshold (${h.profile_match_threshold})`,fixVal:+h.profile_match_threshold.toFixed(2)}})},{keys:["profile_unmatch_threshold","profile_match_threshold"],check:h=>h.profile_unmatch_threshold!=null&&h.profile_match_threshold!=null&&h.profile_unmatch_threshold>=h.profile_match_threshold,fieldErrors:h=>({profile_unmatch_threshold:{msgKey:"conflict.unmatch.unmatch",msgVars:{match:h.profile_match_threshold},msgFb:`Must be below Match Threshold (${h.profile_match_threshold}); otherwise a committed match un-matches instantly`,fixVal:+(h.profile_match_threshold-.05).toFixed(2)},profile_match_threshold:{msgKey:"conflict.unmatch.match",msgVars:{un:h.profile_unmatch_threshold},msgFb:`Must be above Unmatch Threshold (${h.profile_unmatch_threshold})`,fixVal:+(h.profile_unmatch_threshold+.05).toFixed(2)}})},{keys:["anti_wrinkle_exit_power","stop_threshold_w"],check:h=>["washing_machine","dryer","washer_dryer"].includes(h.device_type)&&h.anti_wrinkle_exit_power!=null&&h.stop_threshold_w!=null&&h.anti_wrinkle_exit_power>=h.stop_threshold_w,fieldErrors:h=>({anti_wrinkle_exit_power:{msgKey:"conflict.anti_wrinkle_exit.exit",msgVars:{stop:h.stop_threshold_w},msgFb:`Must be below Stop Threshold (${h.stop_threshold_w} W); otherwise the anti-wrinkle exit power is ignored`,fixVal:+(h.stop_threshold_w*.4).toFixed(1)},stop_threshold_w:{msgKey:"conflict.anti_wrinkle_exit.stop",msgVars:{exit:h.anti_wrinkle_exit_power},msgFb:`Must be above Anti-Wrinkle Exit Power (${h.anti_wrinkle_exit_power} W)`,fixVal:+(h.anti_wrinkle_exit_power*2.5).toFixed(1)}})},{keys:["anti_wrinkle_max_power","start_threshold_w"],check:h=>["washing_machine","dryer","washer_dryer"].includes(h.device_type)&&h.anti_wrinkle_max_power!=null&&h.start_threshold_w!=null&&h.anti_wrinkle_max_power<=h.start_threshold_w,fieldErrors:h=>({anti_wrinkle_max_power:{msgKey:"conflict.anti_wrinkle_max.max",msgVars:{start:h.start_threshold_w},msgFb:`Must be above Start Threshold (${h.start_threshold_w} W); otherwise anti-wrinkle duration limit is bypassed`,fixVal:+(h.start_threshold_w*2).toFixed(0)},start_threshold_w:{msgKey:"conflict.anti_wrinkle_max.start",msgVars:{max:h.anti_wrinkle_max_power},msgFb:`Must be below Anti-Wrinkle Max Power (${h.anti_wrinkle_max_power} W)`,fixVal:+(h.anti_wrinkle_max_power*.5).toFixed(1)}})},{keys:["pump_stuck_duration","no_update_active_timeout"],check:h=>h.device_type==="pump"&&h.pump_stuck_duration!=null&&h.no_update_active_timeout!=null&&h.no_update_active_timeout<=h.pump_stuck_duration,fieldErrors:h=>({pump_stuck_duration:{msgKey:"conflict.pump_stuck.duration",msgVars:{to:h.no_update_active_timeout},msgFb:`Must be less than No-Update Timeout (${h.no_update_active_timeout} s) so the stuck alarm fires before the watchdog kills the cycle`,fixVal:h.no_update_active_timeout-60},no_update_active_timeout:{msgKey:"conflict.pump_stuck.timeout",msgVars:{ps:h.pump_stuck_duration},msgFb:`Must exceed Pump Stuck Duration (${h.pump_stuck_duration} s) so the stuck alarm fires before the cycle is force-stopped`,fixVal:h.pump_stuck_duration+60}})},{keys:["profile_match_min_duration_ratio","profile_match_max_duration_ratio"],check:h=>h.profile_match_min_duration_ratio!=null&&h.profile_match_max_duration_ratio!=null&&h.profile_match_min_duration_ratio>=h.profile_match_max_duration_ratio,fieldErrors:h=>({profile_match_min_duration_ratio:{msgKey:"conflict.duration_ratio.min",msgVars:{max:h.profile_match_max_duration_ratio},msgFb:`Must be less than Max Duration Ratio (${h.profile_match_max_duration_ratio})`,fixVal:+(h.profile_match_max_duration_ratio*.5).toFixed(2)},profile_match_max_duration_ratio:{msgKey:"conflict.duration_ratio.max",msgVars:{min:h.profile_match_min_duration_ratio},msgFb:`Must be greater than Min Duration Ratio (${h.profile_match_min_duration_ratio})`,fixVal:+(h.profile_match_min_duration_ratio*2).toFixed(2)}})},{keys:["end_energy_threshold","stop_threshold_w","off_delay"],check:h=>h.end_energy_threshold!=null&&h.stop_threshold_w!=null&&h.off_delay!=null&&h.off_delay>0&&h.end_energy_threshold({end_energy_threshold:{msgKey:"conflict.end_energy.energy",msgVars:{w:h.stop_threshold_w,d:h.off_delay},msgFb:`Too strict for Stop Threshold (${h.stop_threshold_w} W) over Off Delay (${h.off_delay} s); the cycle can only end through a fallback path`,fixVal:Math.ceil(h.stop_threshold_w*h.off_delay/3600*1e3)/1e3},stop_threshold_w:{msgKey:"conflict.end_energy.stop",msgVars:{e:h.end_energy_threshold,d:h.off_delay},msgFb:`End Energy Threshold (${h.end_energy_threshold} Wh over ${h.off_delay} s) only permits ${+(h.end_energy_threshold*3600/h.off_delay).toFixed(2)} W`,fixVal:Math.floor(h.end_energy_threshold*3600/h.off_delay*10)/10}})}],oe=` :host { display: block; background: var(--primary-background-color); color: var(--primary-text-color); min-height: 100%; font-family: var(--paper-font-body1_-_font-family, Roboto, sans-serif); --wd-radius-sm: 4px; --wd-radius-md: 8px; --wd-radius-lg: 12px; --wd-space-xs: 4px; --wd-space-sm: 6px; --wd-space-md: 10px; --wd-space-lg: 16px; --wd-space-xl: 24px; --wd-font-sm: 0.75em; --wd-font-xs: 0.7em; --wd-white: #fff; --wd-tint-xs: rgba(0,0,0,0.04); --wd-tint-sm: rgba(0,0,0,0.08); --wd-tint-md: rgba(0,0,0,0.12); } .wd-header { display: flex; align-items: center; gap: 12px; padding: 14px 24px; background: var(--app-header-background-color, var(--primary-color)); color: var(--app-header-text-color, #fff); position: sticky; top: 0; z-index: 20; box-shadow: 0 2px 6px rgba(0,0,0,.25); } .wd-header h1 { margin: 0; font-size: 1.25em; font-weight: 600; letter-spacing: .01em; } .wd-logo { flex-shrink: 0; opacity: .95; } .wd-burger { display: none; align-items: center; justify-content: center; background: transparent; border: none; color: inherit; cursor: pointer; padding: 5px; margin: -2px 2px -2px -4px; border-radius: var(--wd-radius-md); flex-shrink: 0; } .wd-burger:hover { background: rgba(255,255,255,.16); } .wd-gear-btn { background: transparent; border: none; color: inherit; cursor: pointer; padding: 5px; margin-left: 4px; border-radius: var(--wd-radius-md); flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; opacity: .8; } .wd-gear-btn:hover { background: rgba(255,255,255,.16); opacity: 1; } @media (max-width: 870px) { .wd-burger { display: inline-flex; } } .wd-burger.wd-burger--force { display: inline-flex; } .wd-header .wd-sub { font-size: .72em; opacity: .75; margin-top: 2px; } .wd-header .wd-ts { margin-left: auto; font-size: .7em; opacity: .65; white-space: nowrap; } .wd-body { max-width: 1160px; margin: 0 auto; padding: 20px 16px 60px; } .wd-chips { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 20px; } .wd-chip { padding: 5px 16px; border-radius: 16px; border: 1px solid var(--divider-color, rgba(0,0,0,.12)); background: var(--card-background-color); color: var(--primary-text-color); cursor: pointer; font-size: .85em; transition: background .15s, color .15s; } .wd-chip:hover { background: var(--secondary-background-color); } .wd-chip.active { background: var(--primary-color); color: var(--wd-white); border-color: var(--primary-color); } .wd-tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--divider-color, rgba(0,0,0,.1)); margin-bottom: 20px; overflow-x: auto; } .wd-tab { padding: 10px 22px; border: none; background: transparent; color: var(--secondary-text-color); font-size: .8em; font-weight: 600; letter-spacing: .07em; text-transform: uppercase; cursor: pointer; border-bottom: 2px solid transparent; transition: color .15s, border-color .15s; white-space: nowrap; } .wd-tab:hover { color: var(--primary-text-color); } .wd-tab.active { color: var(--primary-color); border-bottom-color: var(--primary-color); } .wd-pane { display: none; } .wd-pane.active { display: block; } .wd-card { background: var(--card-background-color); border-radius: var(--wd-radius-lg); padding: 20px 22px; margin-bottom: 16px; box-shadow: var(--ha-card-box-shadow, 0 2px 6px rgba(0,0,0,.08)); } .wd-card-title { margin: 0 0 14px; font-size: .72em; font-weight: 600; letter-spacing: .09em; text-transform: uppercase; color: var(--secondary-text-color); display: flex; align-items: center; gap: 8px; } .wd-card-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 14px; align-items: center; } .wd-badge { display: inline-flex; align-items: center; gap: 7px; padding: 5px 14px; border-radius: 20px; font-size: .85em; font-weight: 500; margin-bottom: 18px; } .wd-dot { width: 8px; height: 8px; border-radius: 50%; background: currentColor; flex-shrink: 0; } .wd-running .wd-dot { animation: wd-pulse 1.4s ease-in-out infinite; } @keyframes wd-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .3; } } .wd-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 12px; margin-bottom: 18px; } .wd-stat { background: var(--secondary-background-color); border-radius: var(--wd-radius-md); padding: 14px 10px; text-align: center; } .wd-stat-val { font-size: 1.5em; font-weight: 600; line-height: 1.1; } .wd-stat-lbl { margin-top: 5px; font-size: .72em; color: var(--secondary-text-color); } .wd-prog-bg { background: var(--secondary-background-color); border-radius: 6px; height: 10px; overflow: hidden; } .wd-prog-fill { height: 100%; background: var(--primary-color); border-radius: 6px; transition: width .6s ease; } .wd-prog-row { display: flex; justify-content: space-between; margin-top: 6px; font-size: .78em; color: var(--secondary-text-color); } /* D1: compact phase timeline below the progress bar */ .wd-ptl-wrap { margin-top: 8px; } .wd-ptl { position: relative; height: 12px; border-radius: 6px; overflow: hidden; background: var(--secondary-background-color); } .wd-ptl-seg { position: absolute; top: 0; bottom: 0; } .wd-ptl-seg-lbl { position: absolute; left: 4px; top: 50%; transform: translateY(-50%); font-size: 8px; line-height: 1; color: var(--wd-white); white-space: nowrap; overflow: hidden; max-width: calc(100% - 6px); text-shadow: 0 0 2px rgba(0,0,0,.55); pointer-events: none; } .wd-ptl-cursor { position: absolute; top: -2px; bottom: -2px; width: 2px; background: var(--primary-text-color, #111); box-shadow: 0 0 0 1px rgba(255,255,255,.6); } .wd-ptl-cur { margin-top: 5px; font-size: .74em; color: var(--secondary-text-color); } .wd-cycle-ctrl { display: flex; gap: 8px; margin-top: 14px; flex-wrap: wrap; } .wd-table { width: 100%; border-collapse: collapse; font-size: .875em; } .wd-table th { text-align: left; padding: 8px 12px; color: var(--secondary-text-color); font-weight: 600; font-size: .72em; letter-spacing: .07em; text-transform: uppercase; border-bottom: 1px solid var(--divider-color); } .wd-table td { padding: 10px 12px; border-bottom: 1px solid var(--divider-color, rgba(0,0,0,.05)); vertical-align: middle; } .wd-table tr:last-child td { border-bottom: none; } .wd-table tbody tr:hover td { background: var(--secondary-background-color); } .wd-table-wrap { overflow-x: auto; } .wd-th-sort { cursor: pointer; user-select: none; white-space: nowrap; } .wd-th-sort:hover { color: var(--primary-color); } .wd-tc-date { white-space: nowrap; color: var(--secondary-text-color); font-size: .82em; } .wd-tc-num { white-space: nowrap; text-align: right; font-variant-numeric: tabular-nums; } /* Dedicated flags/icons column: keep every badge on one line so review/anomaly/source icons never overwrite each other or spill into the profile name. */ .wd-tc-flags { white-space: nowrap; font-size: .9em; } th.wd-tc-flags { color: var(--secondary-text-color); font-weight: 500; } .wd-filter-bar { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; } .wd-filter-input { flex: 1; min-width: 120px; padding: 5px 10px; border-radius: 6px; border: 1px solid var(--divider-color); background: var(--card-background-color); color: var(--primary-text-color); font-size: .84em; } .wd-filter-select { padding: 5px 8px; border-radius: 6px; border: 1px solid var(--divider-color); background: var(--card-background-color); color: var(--primary-text-color); font-size: .84em; } .wd-row-link { cursor: pointer; } .wd-pill { display: inline-block; padding: 2px 9px; border-radius: var(--wd-radius-sm); background: var(--secondary-background-color); color: var(--secondary-text-color); font-size: .78em; } .wd-tag { display: inline-flex; align-items: center; padding: 1px 6px; border-radius: 10px; font-size: .72em; font-weight: 600; vertical-align: middle; background: var(--secondary-background-color); color: var(--secondary-text-color); margin-left: 4px; } .wd-btn { display: inline-flex; align-items: center; gap: 6px; padding: 8px 16px; border-radius: 6px; border: none; cursor: pointer; font-size: .85em; font-weight: 500; transition: opacity .15s; white-space: nowrap; } .wd-btn:hover { opacity: .85; } .wd-btn:disabled { opacity: .55; cursor: default; } .wd-btn-primary { background: var(--primary-color); color: var(--wd-white); } .wd-btn-secondary { background: var(--secondary-background-color); color: var(--primary-text-color); border: 1px solid var(--divider-color); } .wd-btn-danger { background: var(--error-color, #f44336); color: var(--wd-white); } .wd-btn-sm { padding: 4px 10px; font-size: .78em; } .wd-btn-xs { padding: 2px 8px; font-size: .72em; } .wd-spin { display: inline-block; width: 13px; height: 13px; border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%; animation: wd-rot .7s linear infinite; vertical-align: -2px; } @keyframes wd-rot { to { transform: rotate(360deg); } } .wd-field { margin-bottom: 16px; } .wd-field label { display: block; font-size: .82em; font-weight: 600; margin-bottom: 5px; color: var(--secondary-text-color); letter-spacing: .04em; text-transform: uppercase; } .wd-field input[type=text], .wd-field input[type=number], .wd-field select, .wd-field textarea { width: 100%; box-sizing: border-box; padding: 8px 10px; border-radius: 6px; border: 1px solid var(--divider-color, rgba(0,0,0,.2)); background: var(--secondary-background-color); color: var(--primary-text-color); font-size: .9em; font-family: inherit; } .wd-field textarea { min-height: 64px; resize: vertical; } .wd-field input[type=checkbox] { width: auto; margin-right: 8px; } /* Switch-style boolean settings (replaces the old plain checkbox). Scoped under .wd-field-switch so the switch label wins over the generic ".wd-field label" (display:block, higher specificity) rule and stays a centered flex row. */ .wd-field-switch label { margin: 0; } .wd-field-switch .wd-switch-row { display: flex; align-items: center; gap: 10px; min-height: 22px; } /* Switch label: a flex row [toggle][text], vertically centred. The .wd-field .wd-switch-lbl selector is needed to beat .wd-field label (display:block + .82em + uppercase, specificity 0,1,1) which otherwise leaks in and both breaks the vertical centring (align-items is a no-op on a block) and shrinks the label. The bare .wd-switch-lbl covers inline use outside a .wd-field (e.g. adopt). */ .wd-switch-lbl, .wd-field .wd-switch-lbl { display: inline-flex; align-items: center; gap: 10px; cursor: pointer; min-width: 0; margin: 0; font-size: 1rem; font-weight: 400; letter-spacing: normal; text-transform: none; } /* Match the switch label to every other setting name (see .wd-field label). */ .wd-switch-text { font-size: .82em; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--secondary-text-color); } #wd-settings-form .wd-switch-text, #wd-ml-form .wd-switch-text { color: var(--primary-text-color); } /* Normal-case, readable label for switches outside the Settings form (store / panel prefs / access control), whose labels are descriptive sentences. */ .wd-switch-text--plain { font-size: .9em; font-weight: 600; line-height: 1.3; letter-spacing: normal; text-transform: none; color: var(--primary-text-color); } .wd-switch { position: relative; display: inline-flex; flex: 0 0 auto; width: 40px; height: 22px; } .wd-switch input { position: absolute; opacity: 0; width: 0; height: 0; margin: 0; } .wd-switch-slider { position: absolute; inset: 0; border-radius: 22px; background: var(--switch-unchecked-track-color, rgba(120,120,120,.5)); transition: background .2s; } .wd-switch-slider::before { content: ""; position: absolute; height: 16px; width: 16px; left: 3px; top: 3px; border-radius: 50%; background: var(--switch-unchecked-button-color, #fafafa); box-shadow: 0 1px 2px rgba(0,0,0,.3); transition: transform .2s; } .wd-switch input:checked + .wd-switch-slider { background: var(--switch-checked-track-color, var(--primary-color, #03a9f4)); } /* Force a light thumb: some themes set --switch-checked-button-color to the accent, which made the knob vanish into the (also-accent) track. A white thumb reads on any track colour. */ .wd-switch input:checked + .wd-switch-slider::before { transform: translateX(18px); background: #fff; } .wd-switch input:focus-visible + .wd-switch-slider { outline: 2px solid var(--primary-color, #03a9f4); outline-offset: 2px; } /* A11y: a shared keyboard focus ring for all interactive controls (many HA themes suppress the UA default outline). */ .wd-tab:focus-visible, .wd-btn:focus-visible, .wd-chip:focus-visible, .wd-sec-btn:focus-visible, .wd-subtab:focus-visible, .wd-mini-tab:focus-visible, .wd-devcard:focus-visible, [tabindex]:focus-visible, a:focus-visible, select:focus-visible { outline: 2px solid var(--primary-color, #03a9f4); outline-offset: 2px; border-radius: var(--wd-radius-sm); } /* A11y: honor the user's reduced-motion preference \u2014 drop non-essential animation. */ @media (prefers-reduced-motion: reduce) { .wd-dot, .wd-devdot, .wd-rec-active, .wd-spin, .wd-toast { animation: none !important; } * { scroll-behavior: auto !important; } } /* Notifications > Automations: split "New" dropdown + pills. */ .wd-auto-dd summary { cursor: pointer; list-style: none; } .wd-auto-dd summary::-webkit-details-marker { display: none; } .wd-auto-dd summary::marker { content: ''; } .wd-auto-pill { display: inline-flex; align-items: center; gap: 2px; max-width: 100%; background: var(--secondary-background-color); border: 1px solid var(--divider-color); border-radius: 16px; padding: 3px 4px 3px 12px; } .wd-auto-pill-link { text-decoration: none; color: var(--primary-text-color); font-size: .92em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .wd-auto-pill-link:hover { text-decoration: underline; } .wd-auto-pill-x { flex: 0 0 auto; border: none; background: transparent; color: var(--secondary-text-color); cursor: pointer; font-size: 1.15em; line-height: 1; padding: 0 5px; border-radius: 50%; } .wd-auto-pill-x:hover { background: var(--error-color, #f44336); color: var(--wd-white); } .wd-field-hint { font-size: .78em; color: var(--secondary-text-color); margin-top: 4px; } /* Entity-pill multi-picker (compact chips + inline add input) */ .wd-pillbox { display: flex; flex-wrap: wrap; gap: 5px; align-items: center; padding: 5px 6px; min-height: 34px; border: 1px solid var(--divider-color); border-radius: var(--wd-radius-md); background: var(--card-background-color); } .wd-pillbox:focus-within { border-color: var(--primary-color); } .wd-pillbox .wd-pill { display: inline-flex; align-items: center; gap: 4px; max-width: 100%; padding: 2px 4px 2px 9px; font-size: .82em; line-height: 1.4; border-radius: var(--wd-radius-lg); background: var(--primary-color); color: var(--wd-white); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .wd-pill-x { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; padding: 0; border: 0; border-radius: 50%; background: rgba(255,255,255,.25); color: var(--wd-white); font-size: 13px; line-height: 1; cursor: pointer; flex: none; } .wd-pill-x:hover { background: rgba(255,255,255,.45); } .wd-pill-add { flex: 1; min-width: 90px; border: 0 !important; background: transparent !important; padding: 3px 4px !important; font-size: .88em; color: var(--primary-text-color); outline: none; } .wd-form-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 0 20px; } /* Cycle timer list */ .wd-timerlist { display: flex; flex-direction: column; gap: 8px; } .wd-timer-row { display: flex; flex-direction: column; gap: 8px; padding: 10px 12px; border: 1px solid var(--divider-color); border-radius: var(--wd-radius-md); background: var(--card-background-color); } .wd-timer-top { display: flex; align-items: center; gap: 8px; } .wd-timer-top input[type="number"] { width: 70px; flex: 0 0 auto; } .wd-timer-top textarea { flex: 1 1 auto; min-width: 0; box-sizing: border-box; resize: vertical; min-height: 32px; height: 34px; } .wd-timer-footer { display: flex; align-items: center; justify-content: space-between; gap: 8px; } .wd-timer-footer .wd-switch-lbl { display: flex; align-items: center; gap: 8px; cursor: pointer; } .wd-timer-add { align-self: flex-start; margin-top: 4px; } /* Roomier Settings layout (scoped so modals keep their compact spacing) */ #wd-settings-form .wd-form-grid { grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 18px 28px; align-items: start; } #wd-settings-form .wd-field { margin-bottom: 0; background: var(--secondary-background-color); border-radius: 10px; padding: 12px 14px; } #wd-settings-form .wd-field label { color: var(--primary-text-color); } #wd-settings-form .wd-field input[type=text], #wd-settings-form .wd-field input[type=number], #wd-settings-form .wd-field select, #wd-settings-form .wd-field textarea { background: var(--card-background-color); padding: 9px 11px; } #wd-settings-form .wd-subhead { margin: 22px 0 12px; padding-bottom: 6px; border-bottom: 1px solid var(--divider-color); } .wd-sec-intro { font-size: .85em; color: var(--secondary-text-color); margin: 0 0 16px; line-height: 1.5; } .wd-label-row { display: flex; align-items: center; } .wd-tip { display: inline-flex; width: 15px; height: 15px; border-radius: 50%; align-items: center; justify-content: center; font-size: 10px; font-style: italic; background: var(--secondary-background-color); color: var(--secondary-text-color); cursor: help; margin-left: 6px; position: relative; border: 1px solid var(--divider-color); font-weight: 700; text-transform: none; letter-spacing: normal; } .wd-tip-pop { display: none; position: absolute; bottom: 150%; left: 50%; transform: translateX(-50%); width: 264px; background: var(--card-background-color); color: var(--primary-text-color); border: 1px solid var(--divider-color); border-radius: var(--wd-radius-md); padding: 10px 12px; box-shadow: 0 4px 18px rgba(0,0,0,.35); z-index: 60; text-align: left; font-weight: 400; text-transform: none; letter-spacing: normal; } .wd-tip:hover .wd-tip-pop { display: block; } .wd-tip-txt { font-size: 12px; line-height: 1.5; display: block; } .wd-dg { display: block; width: 100%; height: auto; margin-bottom: 8px; background: var(--secondary-background-color); border-radius: 6px; } .wd-dg .ln { fill: none; stroke: var(--primary-color); stroke-width: 2.5; } .wd-dg .ln2 { fill: none; stroke: var(--secondary-text-color); stroke-width: 1.5; opacity: .7; } .wd-dg .ok { fill: none; stroke: var(--success-color, #4caf50); stroke-width: 2; } .wd-dg .bad { fill: none; stroke: var(--error-color, #f44336); stroke-width: 2; } .wd-dg .dash { stroke-dasharray: 4 3; } .wd-dg .fz { fill: var(--primary-color); opacity: .18; } .wd-dg .fw { fill: var(--warning-color, #ff9800); opacity: .2; } .wd-dg .fb { fill: var(--error-color, #f44336); opacity: .14; } .wd-dg text { fill: var(--secondary-text-color); font-size: 9px; } .wd-dg .ax { stroke: var(--divider-color); stroke-width: 1; } .wd-sug { display: flex; align-items: center; gap: 8px; margin-top: 6px; padding: 6px 10px; border-radius: var(--wd-radius-md); font-size: .82em; background: rgba(255,152,0,.10); border: 1px solid rgba(255,152,0,.40); box-sizing: border-box; flex-wrap: wrap; } .wd-sug.wd-sug-split { flex-direction: column; align-items: stretch; gap: 0; padding: 0; overflow: hidden; } .wd-sug-opt { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding: 8px 10px; } .wd-sug-opt:not(:last-child) { border-bottom: 1px solid rgba(255,152,0,.28); } .wd-sug-chip { display: inline-flex; align-items: center; gap: 3px; flex-shrink: 0; font-size: .75em; font-weight: 700; letter-spacing: .04em; padding: 2px 7px; border-radius: 10px; white-space: nowrap; } .wd-sug-chip-obs { background: rgba(255,152,0,.22); } .wd-sug-chip-cal { background: rgba(33,150,243,.18); } .wd-sug-val { font-weight: 700; flex-shrink: 0; } .wd-sug-impact-line { flex-basis: 100%; font-size: .86em; opacity: .70; font-style: italic; margin-top: 2px; } .wd-sug-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; } .wd-sug-sep { display: none; } .wd-sug-impact { display: none; } .wd-sug-use { border: none; background: var(--warning-color, #ff9800); color: var(--wd-white); border-radius: var(--wd-radius-sm); padding: 2px 8px; font-size: .92em; cursor: pointer; flex-shrink: 0; } .wd-sug-lock { border: none; background: transparent; color: var(--secondary-text-color); border-radius: var(--wd-radius-sm); padding: 2px 6px; font-size: .92em; cursor: pointer; flex-shrink: 0; opacity: .65; } .wd-sug-lock:hover { opacity: 1; background: rgba(255,152,0,.15); } .wd-conflict-err { display: flex; flex-direction: column; gap: 4px; margin-top: 5px; } .wd-conflict-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: .8em; color: var(--error-color, #b71c1c); padding: 5px 9px; border-left: 3px solid var(--error-color, #b71c1c); background: rgba(183,28,28,.07); border-radius: 0 5px 5px 0; } .wd-conflict-fix { border: 1px solid var(--error-color, #b71c1c); background: none; color: var(--error-color, #b71c1c); border-radius: var(--wd-radius-sm); padding: 1px 7px; font-size: .92em; cursor: pointer; white-space: nowrap; flex: none; } .wd-conflict-fix:hover { background: var(--error-color, #b71c1c); color: var(--wd-white); } .wd-conflict-sug-note { font-style: italic; opacity: 0.85; flex: none; } #wd-settings-form .wd-field.wd-has-conflict { outline: 2px solid var(--error-color, #b71c1c); outline-offset: -1px; } .wd-rev-sub { display: flex; align-items: center; gap: 6px; margin: 14px 0 6px; font-size: .85em; font-weight: 600; color: var(--primary-text-color); } .wd-rev-tags { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 8px; } .wd-rev-tag { display: flex; align-items: center; gap: 7px; padding: 7px 10px; border-radius: var(--wd-radius-md); background: var(--secondary-background-color); border: 1px solid var(--divider-color); font-size: .85em; cursor: pointer; } .wd-rev-tag input { margin: 0; } .wd-rev-notes { width: 100%; box-sizing: border-box; background: var(--card-background-color); color: var(--primary-text-color); border: 1px solid var(--divider-color); border-radius: var(--wd-radius-md); padding: 9px 11px; font: inherit; resize: vertical; } .wd-sug-banner { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; padding: 12px 16px; border-radius: 10px; margin-bottom: 16px; background: rgba(255,152,0,.12); border: 1px solid rgba(255,152,0,.4); } .wd-subhead { font-size: .76em; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--primary-color); margin: 8px 0 10px; grid-column: 1 / -1; } .wd-section-nav { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 0; } .wd-sec-btn { padding: 5px 14px; border-radius: 14px; border: 1px solid var(--divider-color); background: transparent; color: var(--secondary-text-color); font-size: .8em; cursor: pointer; transition: background .15s; } .wd-sec-btn.active { background: var(--primary-color); color: var(--wd-white); border-color: var(--primary-color); } .wd-level-toggle { display: inline-flex; gap: 4px; } .wd-sec-btn { position: relative; } /* Basic/Advanced slide toggle */ .wd-mode-switch { display: inline-flex; align-items: center; gap: 7px; cursor: pointer; font-size: .82em; user-select: none; white-space: nowrap; } .wd-mode-switch-label { color: var(--secondary-text-color); transition: color .15s; } .wd-mode-switch-label.active { color: var(--primary-color); font-weight: 600; } .wd-toggle-track { position: relative; display: inline-block; width: 36px; height: 20px; flex-shrink: 0; } .wd-toggle-track input { opacity: 0; width: 0; height: 0; position: absolute; } .wd-toggle-knob { position: absolute; inset: 0; border-radius: 20px; background: var(--divider-color); transition: background .2s; } .wd-toggle-knob::after { content: ''; position: absolute; top: 3px; left: 3px; width: 14px; height: 14px; border-radius: 50%; background: #fff; transition: transform .2s; } .wd-toggle-track input:checked + .wd-toggle-knob { background: var(--primary-color); } .wd-toggle-track input:checked + .wd-toggle-knob::after { transform: translateX(16px); } .wd-sec-sug-dot { position: absolute; top: 2px; right: 3px; width: 6px; height: 6px; border-radius: 50%; background: var(--warning-color, #ff9800); display: inline-block; pointer-events: none; } .wd-sec-conf-dot { position: absolute; top: 2px; right: 3px; width: 6px; height: 6px; border-radius: 50%; background: var(--error-color, #b71c1c); display: inline-block; pointer-events: none; } .wd-subtabs { display: flex; gap: 2px; border-bottom: 1px solid var(--divider-color); margin-bottom: 18px; flex-wrap: wrap; } .wd-subtab { padding: 8px 18px; border: none; background: transparent; color: var(--secondary-text-color); font-size: .8em; font-weight: 500; cursor: pointer; border-bottom: 2px solid transparent; transition: color .15s; } .wd-subtab.active { color: var(--primary-color); border-bottom-color: var(--primary-color); } .wd-profiles-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 12px; } .wd-profile-card { background: var(--card-background-color); border-radius: 10px; padding: 16px; border: 1px solid var(--divider-color, rgba(0,0,0,.08)); cursor: pointer; transition: border-color .15s, transform .1s; } .wd-profile-card:hover { border-color: var(--primary-color); transform: translateY(-1px); } button.wd-attn-card, button.wd-profile-card { appearance: none; font: inherit; text-align: left; width: 100%; } button.wd-profile-card { display: block; } .wd-prof-wrap { position: relative; } .wd-profile-name { font-weight: 600; font-size: 1em; margin-bottom: 6px; } .wd-profile-meta { font-size: .8em; color: var(--secondary-text-color); } /* Profile-card header: name on the left, mini power-signature sparkline on the right, both on one line and vertically aligned regardless of badge count. */ .wd-profile-head { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; } .wd-profile-head .wd-profile-name { margin: 0; flex: 1 1 auto; min-width: 0; } /* Status-pill row: wraps cleanly on its own line below the name, and every pill (health / trend / warm-up / imported) shares one uniform compact pill shape. */ .wd-profile-badges { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-bottom: 8px; } .wd-profile-badges .wd-badge { margin: 0; padding: 2px 9px; border-radius: 999px; font-size: .72em; font-weight: 600; gap: 4px; } /* D2: mini duration sparkline on profile cards */ .wd-prof-spark { width: 64px; height: 20px; display: block; flex-shrink: 0; } /* Community Store */ .wd-store-crumbs { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-bottom: 14px; font-size: .85em; } .wd-crumb { background: none; border: none; color: var(--primary-color); cursor: pointer; padding: 2px 4px; font: inherit; } .wd-crumb:hover { text-decoration: underline; } .wd-crumb.active { color: var(--primary-text-color); font-weight: 700; cursor: default; } .wd-crumb-sep { color: var(--secondary-text-color); } .wd-store-search { display: flex; gap: 8px; margin-bottom: 14px; flex-wrap: wrap; } .wd-store-search input { flex: 1; min-width: 180px; padding: 8px 11px; border-radius: 6px; border: 1px solid var(--divider-color); background: var(--secondary-background-color); color: var(--primary-text-color); font-size: .9em; } .wd-store-list { display: flex; flex-direction: column; gap: 8px; } /* Browse rows (appliances / programs): tappable list rows with a hover affordance and a chevron, instead of flat cards. */ .wd-store-rows { display: flex; flex-direction: column; gap: 6px; } .wd-store-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; text-align: left; width: 100%; cursor: pointer; padding: 11px 14px; background: var(--secondary-background-color); border: 1px solid var(--divider-color); border-radius: var(--wd-radius-md); color: var(--primary-text-color); transition: border-color .12s ease, background .12s ease; } .wd-store-row:hover { border-color: var(--primary-color); background: var(--card-background-color); } .wd-store-row-main { display: flex; flex-direction: column; gap: 3px; min-width: 0; } .wd-store-row-title { font-weight: 600; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; } .wd-store-row-sub { display: flex; align-items: center; gap: 10px; font-size: .8em; color: var(--secondary-text-color); } .wd-store-chip { background: var(--accent-dim, rgba(0,180,216,.14)); color: var(--primary-color); border-radius: 999px; padding: 1px 8px; font-size: .92em; text-transform: capitalize; } .wd-store-fav { color: var(--warning-color, #f0b429); } .wd-store-row-arrow { color: var(--secondary-text-color); font-size: 1.3em; flex-shrink: 0; } .wd-store-cycle-top { display: flex; align-items: center; gap: 12px; } .wd-store-cycle-stats { flex: 1; min-width: 0; } .wd-store-spark { width: 120px; height: 36px; flex-shrink: 0; display: block; background: var(--secondary-background-color); border-radius: 6px; } .wd-store-conn { display: flex; align-items: center; gap: 10px; margin-top: 12px; flex-wrap: wrap; } .wd-store-actions { display: flex; gap: 8px; margin-top: 12px; flex-wrap: wrap; } .wd-tag-pending { background: rgba(56,139,253,.18); color: var(--info-color, #58a6ff); } .wd-tag-approved { background: rgba(63,185,80,.18); color: var(--success-color, #3fb950); } .wd-store-picker-detail { margin-top: 6px; font-size: .85em; color: var(--secondary-text-color); display: flex; flex-wrap: wrap; gap: 4px 10px; align-items: center; } .wd-store-picker-actions { display: flex; align-items: center; gap: 8px; flex-basis: 100%; margin-top: 4px; flex-wrap: wrap; } .wd-star-row { display: inline-flex; gap: 2px; } .wd-star-btn { background: none; border: none; cursor: pointer; color: var(--warning-color, #f0b429); font-size: 1.1em; padding: 0 1px; line-height: 1; } .wd-star-btn:hover { transform: scale(1.15); } /* Share-device selection tree (profile -> its reference cycles). */ .wd-sd-tree { display: flex; flex-direction: column; gap: 8px; max-height: 44vh; overflow-y: auto; margin-bottom: 16px; } .wd-sd-group { border: 1px solid var(--divider-color); border-radius: var(--wd-radius-md); background: var(--secondary-background-color); overflow: hidden; } .wd-sd-prof { display: flex; align-items: center; gap: 8px; padding: 9px 12px; cursor: pointer; font-weight: 600; } .wd-sd-prof-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .wd-sd-count { font-size: .8em; font-weight: 400; color: var(--secondary-text-color); } .wd-sd-cycles { display: flex; flex-direction: column; border-top: 1px solid var(--divider-color); } .wd-sd-cyc { display: flex; align-items: center; gap: 8px; padding: 6px 12px 6px 28px; cursor: pointer; font-size: .9em; } .wd-sd-cyc:hover, .wd-sd-prof:hover { background: var(--card-background-color); } .wd-sd-cyc-meta { color: var(--secondary-text-color); } .wd-sd-phase { display: flex; align-items: center; gap: 8px; padding: 6px 12px; border-top: 1px solid var(--divider-color); cursor: pointer; font-size: .85em; color: var(--secondary-text-color); } .wd-sd-phase:hover { background: var(--card-background-color); } .wd-sd-settings { display: flex; align-items: center; gap: 8px; padding: 10px 2px 2px; cursor: pointer; font-size: .9em; } .wd-sd-consent { display: flex; align-items: flex-start; gap: 8px; padding: 10px 2px 2px; cursor: pointer; font-size: .9em; } .wd-sd-group-nocyc { opacity: .65; } .wd-sd-prof-disabled { display: flex; align-items: baseline; gap: 8px; padding: 9px 12px; font-weight: 600; flex-wrap: wrap; } .wd-sd-nocyc-note { font-size: .8em; font-weight: 400; color: var(--secondary-text-color); } .wd-share-guide { margin-bottom: 10px; border: 1px solid var(--divider-color); border-radius: var(--wd-radius-md); overflow: hidden; } .wd-share-guide > summary { padding: 8px 12px; cursor: pointer; font-size: .85em; font-weight: 600; color: var(--secondary-text-color); list-style: none; } .wd-share-guide > summary::-webkit-details-marker { display: none; } .wd-share-guide > summary::before { content: '\u25B6 '; font-size: .7em; } .wd-share-guide[open] > summary::before { content: '\u25BC '; } .wd-share-guide-list { margin: 0; padding: 4px 12px 10px 28px; font-size: .85em; color: var(--secondary-text-color); line-height: 1.5; } .wd-share-guide-list li { margin-bottom: 4px; } .wd-linkbtn { background: none; border: none; padding: 0; color: var(--primary-color); cursor: pointer; font: inherit; text-decoration: underline; } .wd-gear-body { margin-top: 12px; } .wd-empty { text-align: center; padding: 48px 24px; color: var(--secondary-text-color); } .wd-empty .wd-icon { font-size: 3em; margin-bottom: 10px; } .wd-error-state { display: flex; align-items: center; gap: 10px; padding: 10px 14px; margin-bottom: 10px; border-radius: var(--wd-radius-md); background: var(--secondary-background-color); border: 1px solid var(--divider-color); color: var(--error-color, #b71c1c); font-size: .9em; } .wd-info { font-size: .9em; color: var(--secondary-text-color); line-height: 1.6; margin: 0; } .wd-overlay { position: fixed; inset: 0; background: rgba(0,0,0,.5); z-index: 100; display: flex; align-items: center; justify-content: center; } .wd-modal { background: var(--card-background-color); border-radius: var(--wd-radius-lg); padding: 24px; max-width: 480px; width: calc(100% - 32px); max-height: 90vh; overflow-y: auto; box-shadow: 0 8px 32px rgba(0,0,0,.3); } .wd-modal-lg { max-width: 880px; } .wd-modal h2 { margin: 0 0 16px; font-size: 1.1em; display: flex; align-items: center; gap: 10px; } .wd-modal-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 20px; flex-wrap: wrap; } .wd-canvas-wrap { margin: 10px 0; background: var(--secondary-background-color); border-radius: var(--wd-radius-md); padding: 6px; } .wd-canvas-wrap canvas { width: 100%; height: 240px; display: block; touch-action: none; cursor: crosshair; } .wd-mode-bar { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 8px; } .wd-mini-tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--divider-color); margin-bottom: 16px; flex-wrap: wrap; } .wd-mini-tab { padding: 7px 16px; border: none; background: transparent; color: var(--secondary-text-color); font-size: .82em; font-weight: 600; cursor: pointer; border-bottom: 2px solid transparent; } .wd-mini-tab.active { color: var(--primary-color); border-bottom-color: var(--primary-color); } .wd-kv { display: grid; grid-template-columns: repeat(auto-fit, minmax(110px, 1fr)); gap: 10px; margin: 4px 0 14px; } .wd-kv-item { background: var(--secondary-background-color); border-radius: var(--wd-radius-md); padding: 10px; text-align: center; } .wd-kv-val { font-size: 1.25em; font-weight: 700; } .wd-kv-lbl { font-size: .7em; color: var(--secondary-text-color); margin-top: 3px; } .wd-seg-row, .wd-phase-row { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; flex-wrap: wrap; } .wd-swatch { width: 12px; height: 12px; border-radius: 3px; flex-shrink: 0; display: inline-block; } .wd-toast { position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%); z-index: 200; padding: 10px 20px; border-radius: var(--wd-radius-md); font-size: .9em; font-weight: 500; box-shadow: 0 4px 12px rgba(0,0,0,.25); animation: wd-toast-in .2s ease; } @keyframes wd-toast-in { from { opacity: 0; transform: translateX(-50%) translateY(10px); } } .wd-toast-success { background: var(--success-color, #4caf50); color: var(--wd-white); } .wd-toast-error { background: var(--error-color, #f44336); color: var(--wd-white); } .wd-toast-info { background: var(--info-color, #2196f3); color: var(--wd-white); } /* D4: undo toast \u2014 action button + row layout */ .wd-toast { display: flex; align-items: center; gap: 14px; } .wd-toast-action { background: rgba(255,255,255,.22); color: inherit; border: none; border-radius: 6px; padding: 5px 12px; font: inherit; font-weight: 700; cursor: pointer; text-transform: uppercase; letter-spacing: .04em; font-size: .85em; } .wd-toast-action:hover { background: rgba(255,255,255,.34); } /* D7: "changed since last save" marker beside a settings field label */ .wd-chg-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--info-color, #2196f3); margin-left: 6px; flex-shrink: 0; cursor: help; } .wd-diag-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 10px; margin-bottom: 16px; } .wd-diag-stat { background: var(--secondary-background-color); border-radius: var(--wd-radius-md); padding: 12px; text-align: center; } .wd-diag-val { font-size: 1.6em; font-weight: 700; } .wd-diag-lbl { font-size: .72em; color: var(--secondary-text-color); margin-top: 4px; } .wd-feedback-item { display: flex; align-items: center; gap: 10px; padding: 10px 0; border-bottom: 1px solid var(--divider-color); } .wd-feedback-item:last-child { border-bottom: none; } .wd-feedback-body { flex: 1; } .wd-feedback-profile { font-weight: 600; } .wd-feedback-meta { font-size: .78em; color: var(--secondary-text-color); } .wd-rec-status { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; } .wd-rec-dot { width: 12px; height: 12px; border-radius: 50%; flex-shrink: 0; } .wd-rec-active { background: var(--error-color, #f44336); animation: wd-pulse 1s ease-in-out infinite; } .wd-rec-ready { background: var(--success-color, #4caf50); } .wd-rec-idle { background: var(--disabled-color, #bdbdbd); } /* Graph hover tooltip (follows the cursor) */ .wd-gtip { position: fixed; z-index: 300; display: none; pointer-events: none; background: var(--card-background-color); color: var(--primary-text-color); border: 1px solid var(--divider-color); border-radius: var(--wd-radius-md); padding: 7px 10px; font-size: 12px; line-height: 1.5; box-shadow: 0 4px 16px rgba(0,0,0,.4); white-space: nowrap; } .wd-gtip b { font-weight: 700; } /* Status chart legend + toggles */ .wd-leg { display: flex; gap: 14px; flex-wrap: wrap; margin-top: 10px; font-size: .8em; color: var(--secondary-text-color); } .wd-leg-i { display: inline-flex; align-items: center; gap: 6px; } .wd-leg-i input { margin: 0 2px 0 0; width: auto; } .wd-leg-sw { width: 16px; height: 3px; border-radius: 2px; display: inline-block; } /* Status program selector */ .wd-prog-ctl { display: flex; align-items: center; gap: 10px; margin-bottom: 16px; flex-wrap: wrap; } .wd-prog-ctl label { font-size: .72em; text-transform: uppercase; letter-spacing: .08em; color: var(--secondary-text-color); margin: 0; } .wd-prog-ctl select { padding: 8px 11px; border-radius: 6px; border: 1px solid var(--divider-color); background: var(--secondary-background-color); color: var(--primary-text-color); min-width: 200px; font-size: .9em; } .wd-prog-tag { font-size: .78em; padding: 3px 9px; border-radius: 10px; } .wd-prog-tag.auto { background: rgba(76,175,80,.18); color: var(--success-color, #4caf50); } .wd-prog-tag.manual { background: rgba(255,152,0,.2); color: var(--warning-color, #ff9800); } /* Status-rich device selector */ .wd-devbar { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 20px; } .wd-devcard { display: flex; align-items: center; gap: 9px; padding: 9px 13px; border-radius: var(--wd-radius-lg); border: 1px solid var(--divider-color); background: var(--card-background-color); color: var(--primary-text-color); cursor: pointer; font-size: .9em; } .wd-devcard.active { border-color: var(--primary-color); box-shadow: 0 0 0 1px var(--primary-color); } .wd-devadd { border-style: dashed; color: var(--secondary-text-color); } .wd-devadd:hover { border-color: var(--primary-color); color: var(--primary-color); } .wd-devdot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; } .wd-devdot.run { animation: wd-pulse 1.4s ease-in-out infinite; } .wd-devname { font-weight: 600; } .wd-devsub { font-size: .72em; color: var(--secondary-text-color); } .wd-dbadge { font-size: .72em; padding: 1px 7px; border-radius: 10px; background: var(--secondary-background-color); } .wd-dbadge.rec { background: var(--error-color, #f44336); color: var(--wd-white); } .wd-dbadge.sug { background: rgba(255,152,0,.22); } .wd-dbadge.fb { background: rgba(33,150,243,.22); } .wd-dbadge.conf { background: rgba(183,28,28,.18); color: var(--error-color, #b71c1c); } /* Attention cards (status dashboard) */ .wd-attn { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 10px; margin-bottom: 16px; } .wd-attn-card { display: flex; align-items: center; gap: 11px; padding: 12px 14px; border-radius: 10px; background: var(--card-background-color); border: 1px solid var(--divider-color); cursor: pointer; transition: border-color .15s; } .wd-attn-card:hover { border-color: var(--primary-color); } .wd-attn-icon { font-size: 1.5em; line-height: 1; } .wd-attn-body { flex: 1; min-width: 0; } .wd-attn-title { font-weight: 600; } .wd-attn-sub { font-size: .76em; color: var(--secondary-text-color); } /* F1 first-run onboarding card (Status power-chart area) */ .wd-onboard { margin-top: 12px; padding: 16px; border-radius: 10px; border: 1px dashed var(--divider-color); background: var(--secondary-background-color); } .wd-onboard .wd-card-title { margin-top: 0; } .wd-onboard-skip { font-size: .8em; color: var(--secondary-text-color); text-decoration: underline; cursor: pointer; } .wd-onboard-skip:hover { color: var(--primary-color); } /* Setup card (replaces getting-started card, phases 0-3) and phase-4 chip */ .wd-setup-card { margin-top: 12px; padding: 16px; border-radius: 10px; border: 1px solid var(--primary-color, #03a9f4); background: color-mix(in srgb, var(--primary-color, #03a9f4) 8%, var(--card-background-color, var(--ha-card-background))); } .wd-setup-card .wd-card-title { margin-top: 0; } .wd-setup-chip { display: inline-flex; align-items: center; gap: 6px; padding: 5px 12px; border-radius: 20px; font-size: .82em; font-weight: 600; cursor: pointer; border: 1px solid var(--divider-color); background: var(--secondary-background-color); margin-top: 12px; } .wd-setup-chip--healthy { border-color: var(--success-color, #4caf50); background: color-mix(in srgb, var(--success-color, #4caf50) 10%, var(--card-background-color, transparent)); } .wd-setup-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; } .wd-setup-dot--green { background: var(--success-color, #4caf50); } .wd-link { background: none; border: none; padding: 0; color: var(--primary-color); cursor: pointer; font: inherit; text-decoration: underline; display: inline; } .wd-link:hover { opacity: .8; } /* Logs page */ .wd-logbar { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-bottom: 12px; } .wd-logs { font-family: monospace; font-size: .76em; background: var(--secondary-background-color); border-radius: var(--wd-radius-md); padding: 10px; height: 56vh; min-height: 140px; overflow: auto; resize: vertical; } #wd-log-lines-page { height: auto; min-height: 200px; resize: none; } /* Grouped stat blocks (profile overview) */ .wd-sg-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 10px; margin: 4px 0 16px; } .wd-sg { background: var(--secondary-background-color); border-radius: 10px; padding: 14px; } .wd-sg-h { font-size: .7em; text-transform: uppercase; letter-spacing: .08em; color: var(--secondary-text-color); margin-bottom: 6px; } .wd-sg-main { font-size: 1.55em; font-weight: 700; line-height: 1.1; } .wd-sg-main span { font-size: .5em; font-weight: 400; color: var(--secondary-text-color); margin-left: 4px; } .wd-sg-sub { font-size: .78em; color: var(--secondary-text-color); margin-top: 5px; line-height: 1.5; } .wd-logline { padding: 2px 0; border-bottom: 1px solid var(--divider-color); white-space: pre-wrap; word-break: break-word; } .wd-logline:last-child { border-bottom: none; } /* Entity combobox */ .wd-combo { position: relative; width: 100%; } .wd-combo-drop { position: absolute; top: 100%; left: 0; right: 0; z-index: 60; background: var(--card-background-color,#fff); border: 1px solid var(--divider-color); border-radius: 6px; box-shadow: 0 4px 14px rgba(0,0,0,.18); max-height: 220px; overflow-y: auto; margin-top: 3px; } .wd-combo-item { padding: 7px 12px; cursor: pointer; font-size: .86em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .wd-combo-item:hover, .wd-combo-item.kbd { background: var(--secondary-background-color); } .wd-combo-row { display: flex; gap: 6px; align-items: center; } .wd-combo-row .wd-combo { flex: 1 1 auto; } .wd-addbtn { flex: 0 0 auto; width: 34px; height: 34px; border-radius: var(--wd-radius-md); border: 1px solid var(--divider-color); background: var(--secondary-background-color); color: var(--primary-text-color); font-size: 1.25em; line-height: 1; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; } .wd-addbtn:hover { background: var(--primary-color); color: #fff; border-color: var(--primary-color); } .wd-loglvl { font-weight: 700; margin-right: 6px; } .wd-logcomp { display: inline-block; font-size: .72em; color: var(--secondary-text-color); background: var(--secondary-background-color); border-radius: 4px; padding: 0 5px; margin-right: 6px; } .wd-logdev { display: inline-block; font-size: .72em; color: var(--primary-color); margin-right: 6px; } .wd-logts { color: var(--secondary-text-color); margin-right: 6px; } .wd-lvl-ERROR, .wd-lvl-CRITICAL { color: var(--error-color, #f44336); } .wd-lvl-WARNING { color: var(--warning-color, #ff9800); } .wd-lvl-INFO { color: var(--info-color, #2196f3); } .wd-lvl-DEBUG { color: var(--secondary-text-color); } /* Compact cycle list */ .wd-clist { display: flex; flex-direction: column; } .wd-crow { display: flex; align-items: center; gap: 10px; padding: 9px 6px; border-bottom: 1px solid var(--divider-color); cursor: pointer; } .wd-crow:hover { background: var(--secondary-background-color); } .wd-crow:last-child { border-bottom: none; } .wd-cmain { flex: 1; min-width: 0; overflow: hidden; } .wd-cprog { font-weight: 600; } .wd-cdate { font-size: .74em; color: var(--secondary-text-color); } .wd-cmeta { text-align: right; font-size: .76em; color: var(--secondary-text-color); white-space: nowrap; } /* Responsive / touch (portrait, phones, side panel) */ @media (max-width: 680px) { .wd-body { padding: 12px 10px 64px; } .wd-card { padding: 14px; margin-bottom: 12px; } .wd-form-grid { grid-template-columns: 1fr; } .wd-stats { grid-template-columns: repeat(2, 1fr); } .wd-kv { grid-template-columns: repeat(2, 1fr); } .wd-tab { padding: 9px 13px; } .wd-modal { padding: 16px; width: calc(100% - 18px); } .wd-modal-lg { max-width: 100%; } .wd-canvas-wrap canvas { height: 200px; } .wd-header { padding: 12px 14px; } .wd-btn { padding: 9px 15px; } /* larger touch targets */ .wd-tip-pop { width: 210px; } .wd-pg-lane-lbl { flex: 0 1 120px; max-width: 120px; } #wd-settings-form .wd-form-grid { grid-template-columns: 1fr; gap: 12px 0; } } /* Log drawer */ .wd-shell { display: flex; flex-direction: column; min-height: 100%; } .wd-content-row { display: flex; flex: 1; overflow: hidden; min-height: 0; } .wd-main { flex: 1; overflow-y: auto; min-width: 0; } .wd-log-drawer { position: relative; width: 0; overflow: hidden; transition: width .28s cubic-bezier(.4,0,.2,1); border-left: 1px solid var(--divider-color); display: flex; flex-direction: column; background: var(--primary-background-color); } .wd-log-drawer.open { width: 380px; } .wd-log-resize { position: absolute; left: 0; top: 0; bottom: 0; width: 6px; cursor: ew-resize; z-index: 2; transition: background .15s; } .wd-log-resize:hover, .wd-log-resize.dragging { background: var(--primary-color, #03a9f4); opacity: .35; } .wd-log-drawer-head { display: flex; align-items: center; justify-content: space-between; padding: 10px 14px; border-bottom: 1px solid var(--divider-color); font-weight: 600; font-size: .9em; flex-shrink: 0; white-space: nowrap; } .wd-log-drawer-body { flex: 1; overflow-y: auto; padding: 10px 14px; min-width: 0; } .wd-log-close-btn { background: none; border: none; cursor: pointer; color: inherit; opacity: .65; padding: 3px 6px; border-radius: var(--wd-radius-sm); font-size: 1.1em; line-height: 1; } .wd-log-close-btn:hover { opacity: 1; background: var(--secondary-background-color); } .wd-gear-btn.log-active { background: rgba(255,255,255,.22); } @media (max-width: 680px) { .wd-log-drawer.open { width: 100vw !important; position: fixed; top: 0; right: 0; bottom: 0; z-index: 30; border-left: none; } .wd-log-resize { display: none; } } .wd-pg-delta-up { color: var(--success-color, #4caf50); font-weight: 700; } .wd-pg-delta-down { color: var(--error-color, #f44336); font-weight: 700; } .wd-pg-delta-flat { color: var(--secondary-text-color); } /* F3: Unified Playground */ .wd-pg-canvas-wrap { position: relative; width: 100%; } #wd-pg-canvas { display: block; width: 100%; height: 330px; cursor: crosshair; border-radius: 6px; background: var(--secondary-background-color); margin: 10px 0 0; } .wd-pg-strip { display: flex; align-items: center; gap: 10px; padding: 8px 2px; font-size: .88em; font-variant-numeric: tabular-nums; flex-wrap: wrap; border-bottom: 1px solid var(--divider-color, rgba(127,127,127,.2)); margin-bottom: 12px; } .wd-pg-strip-state { padding: 2px 10px; border-radius: 20px; font-weight: 700; font-size: .83em; white-space: nowrap; } .wd-pg-strip-pbar { display: inline-flex; align-items: center; gap: 5px; } .wd-pg-strip-track { width: 60px; height: 6px; background: var(--secondary-background-color); border-radius: 3px; overflow: hidden; display: inline-block; vertical-align: middle; } .wd-pg-strip-fill { height: 100%; background: var(--primary-color); border-radius: 3px; transition: width .15s; } .wd-pg-params { display: flex; flex-direction: column; gap: 2px; } .wd-pg-param-row { display: flex; align-items: center; gap: 6px; } .wd-pg-param-lbl { flex: 1; font-size: .83em; color: var(--secondary-text-color); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .wd-pg-param-inp { width: 76px; flex: 0 0 76px; } .wd-pg-param-drag { font-size: .75em; color: var(--primary-color); cursor: default; flex: 0 0 12px; } .wd-pg-score-bar-row { display: flex; align-items: center; gap: 6px; font-size: .82em; margin: 2px 0; } .wd-pg-score-bar-lbl { flex: 0 0 80px; color: var(--secondary-text-color); } .wd-pg-score-bar-track { flex: 1; height: 6px; background: var(--secondary-background-color); border-radius: 3px; overflow: hidden; } .wd-pg-score-bar-fill { height: 100%; border-radius: 3px; } .wd-pg-score-bar-val { flex: 0 0 42px; text-align: right; font-variant-numeric: tabular-nums; color: var(--secondary-text-color); } .wd-pg-cand-row { display: flex; align-items: center; gap: 6px; font-size: .82em; margin: 3px 0; } .wd-pg-cand-name { flex: 0 0 110px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .wd-pg-cand-track { flex: 1; height: 7px; background: var(--secondary-background-color); border-radius: var(--wd-radius-sm); overflow: hidden; } .wd-pg-cand-fill { height: 100%; border-radius: var(--wd-radius-sm); } .wd-pg-cand-pct { flex: 0 0 34px; text-align: right; color: var(--secondary-text-color); } /* Playground: settings control panel (live-settings source + named presets) */ .wd-pg-ctrl { border: 1px solid var(--divider-color, rgba(127,127,127,.25)); border-radius: var(--wd-radius-md, 10px); padding: 8px 10px; margin: 0 0 10px; background: var(--secondary-background-color); } .wd-pg-ctrl .wd-btn { white-space: nowrap; } .wd-pg-preset-sel { min-width: 150px; max-width: 220px; font-size: .82em; } .wd-pg-preset-name { flex: 1 1 150px; min-width: 120px; max-width: 220px; font-size: .82em; } /* Per-setting publish arrow. The empty slot keeps every row's value column aligned whether or not that row currently has a publishable change. */ .wd-pg-pub { width: 20px; height: 20px; flex: 0 0 20px; padding: 0; border: none; border-radius: 5px; cursor: pointer; background: var(--primary-color); color: var(--text-primary-color, #fff); font-size: .8em; line-height: 1; } .wd-pg-pub:hover { filter: brightness(1.15); } .wd-pg-pub-slot { width: 20px; flex: 0 0 20px; display: inline-block; } /* Playground: unified workbench (graph+settings always on top) + "Across your cycles" drawer with History/Optimize sub-tabs. */ .wd-pg-drawer { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--divider-color, rgba(127,127,127,.25)); } .wd-pg-drawer-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin: 0 0 12px; } .wd-pg-subtabs { display: inline-flex; gap: 2px; padding: 3px; border-radius: 10px; background: var(--secondary-background-color); } .wd-pg-subtab { border: none; background: transparent; color: var(--secondary-text-color); font: inherit; font-size: .84em; font-weight: 600; padding: 5px 13px; border-radius: 8px; cursor: pointer; } .wd-pg-subtab:hover { color: var(--primary-text-color); } .wd-pg-subtab.active { background: var(--card-background-color, var(--primary-background-color)); color: var(--primary-color); box-shadow: 0 1px 3px rgba(0,0,0,.12); } .wd-pg-hrow { cursor: pointer; } .wd-pg-hrow:hover td { background: var(--secondary-background-color); } .wd-pg-hrow.selected td { background: color-mix(in srgb, var(--primary-color) 14%, transparent); box-shadow: inset 2px 0 0 var(--primary-color); } .wd-pg-sim-grid { display: grid; grid-template-columns: 1.4fr 1fr; gap: 16px; margin-top: 4px; } .wd-pg-sim-main, .wd-pg-sim-side { display: flex; flex-direction: column; gap: 4px; min-width: 0; } .wd-pg-simbar { height: 6px; border-radius: 3px; background: var(--secondary-background-color); overflow: hidden; margin: 6px 0 0; } .wd-pg-simbar-fill { height: 100%; width: 40%; border-radius: 3px; background: var(--primary-color); animation: wd-pg-indeterminate 1.1s ease-in-out infinite; } @keyframes wd-pg-indeterminate { 0% { margin-left: -40%; } 100% { margin-left: 100%; } } .wd-pg-batchbar-fill { height: 100%; width: 0%; border-radius: 3px; background: var(--primary-color); transition: width .18s ease; } /* Header activity pills (background-task registry) */ .wd-task-pills { display: inline-flex; gap: 6px; align-items: center; flex-wrap: wrap; margin: 0 0 0 12px; } .wd-task-pill { display: inline-flex; align-items: center; gap: 6px; padding: 3px 4px 3px 9px; border-radius: 12px; background: rgba(255,255,255,.16); color: var(--app-header-text-color, #fff); font-size: .78em; line-height: 1; } .wd-task-pill-lbl { font-weight: 600; max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .wd-task-pill-pct { font-variant-numeric: tabular-nums; opacity: .95; } .wd-task-pill-eta { opacity: .75; } .wd-task-pill-x { border: none; background: rgba(0,0,0,.18); color: inherit; width: 16px; height: 16px; border-radius: 50%; cursor: pointer; font-size: .9em; line-height: 1; display: inline-flex; align-items: center; justify-content: center; padding: 0; } .wd-task-pill-x:hover { background: rgba(0,0,0,.32); } .wd-task-pill--cancelling { background: rgba(255,160,0,.28); } .wd-task-pill-cancelling { opacity: .9; font-style: italic; } .wd-task-pill-x--cancelling { opacity: .4; cursor: default; pointer-events: none; } .wd-task-spin { width: 10px; height: 10px; border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%; animation: wd-spin-kf .8s linear infinite; opacity: .9; } @keyframes wd-spin-kf { to { transform: rotate(360deg); } } .wd-pg-batchrow { display: flex; align-items: center; gap: 10px; margin: 6px 0 8px; } .wd-pg-batchrow .wd-pg-simbar { flex: 1; margin: 0; } #wd-pg-canvas.wd-pg-panning { cursor: grabbing; } .wd-pg-alerts-card { background: var(--secondary-background-color); border-radius: 10px; padding: 12px; } .wd-pg-outcome-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; } .wd-pg-outcome-item { text-align: center; background: var(--card-background-color, var(--primary-background-color)); border-radius: 8px; padding: 8px 4px; } .wd-pg-outcome-val { font-size: 1.05em; font-weight: 700; } .wd-pg-outcome-lbl { font-size: .68em; color: var(--secondary-text-color); text-transform: uppercase; letter-spacing: .04em; margin-top: 2px; } .wd-pg-alert { border-left: 3px solid var(--info-color, #2196f3); padding: 4px 0 4px 10px; } /* History table */ .wd-pg-htable { width: 100%; border-collapse: collapse; font-size: .84em; } .wd-pg-htable th { text-align: left; font-weight: 600; color: var(--secondary-text-color); padding: 6px 8px; border-bottom: 1px solid var(--divider-color, rgba(127,127,127,.2)); font-size: .82em; } .wd-pg-htable td { padding: 6px 8px; border-bottom: 1px solid var(--divider-color, rgba(127,127,127,.12)); } .wd-pg-htable tr[data-action] { cursor: pointer; } .wd-pg-htable tr[data-action]:hover { background: var(--secondary-background-color); } .wd-pg-diffbadge { display: inline-flex; align-items: center; gap: 5px; padding: 3px 10px; border-radius: 20px; font-size: .82em; font-weight: 600; margin: 0 6px 6px 0; } /* Sweep heatmap */ @media (max-width: 720px) { .wd-pg-sim-grid { grid-template-columns: 1fr; } } @media (max-width: 640px) { .wd-pg-strip { gap: 7px; font-size: .82em; } } `;function ot(h){if(h==null||h<0)return"-";const t=Math.floor(h/3600),e=Math.floor(h%3600/60),s=Math.floor(h%60);return t>0?`${t}h ${e}m`:e>0?`${e}m ${s}s`:`${s}s`}function Ut(h){return h==null?"-":h>=100?`${Math.round(h)} W`:`${h.toFixed(1)} W`}function gt(h){return h==null?"-":`${h.toFixed(2)} kWh`}let Gt="relative";const le=3700;function Kt(h){const t=e=>String(e).padStart(2,"0");return`${h.getFullYear()}-${t(h.getMonth()+1)}-${t(h.getDate())}`}function Xt(h){const t=new Date;return t.setDate(t.getDate()-h),Kt(t)}function de(){return Kt(new Date)}function zt(){return Xt(10)}function ce(){return Xt(le-1)}function he(h){const t=Math.round((h-Date.now())/1e3);let e;try{e=new Intl.RelativeTimeFormat(void 0,{numeric:"auto"})}catch{return Yt(h)}const s=Math.abs(t),r=[["year",31536e3],["month",2592e3],["week",604800],["day",86400],["hour",3600],["minute",60]];for(const[n,a]of r)if(s>=a)return e.format(Math.round(t/a),n);return e.format(t,"second")}function Yt(h){return new Date(h).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"})}function st(h,t){if(!h)return"-";let e;if(typeof h=="number")e=h>=1e12?h:h*1e3;else{const s=String(h),r=/^(\d{4})-(\d{2})-(\d{2})$/.exec(s);e=r?new Date(+r[1],+r[2]-1,+r[3]).getTime():new Date(s).getTime()}return isNaN(e)?"-":(t||Gt)==="relative"?he(e):Yt(e)}function Pt(h){if(h==null)return"unknown error";if(h instanceof Error)return`${h.name}: ${h.message}`;const t=h.code!=null?String(h.code):"",e=h.message!=null?String(h.message):"";if(!t&&!e)try{return JSON.stringify(h)}catch{return String(h)}const s=t==="unknown_command"?" (the integration is probably still starting up; this should stop on its own)":"";return`${t||"error"}${e?": "+e:""}${s}`}function d(h){return String(h??"").replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function pe(h){const t=String(h??"").trim();return/^https?:\/\//i.test(t)?t:""}function St(h,t){const e=String(h??"").trim();let s=e.match(/^#([0-9a-fA-F]{3,8})$/);if(s){let r=s[1];if((r.length===3||r.length===4)&&(r=r.split("").map(n=>n+n).join("")),r.length===6||r.length===8){const n=parseInt(r.slice(0,2),16),a=parseInt(r.slice(2,4),16),o=parseInt(r.slice(4,6),16);return`rgba(${n}, ${a}, ${o}, ${t})`}}if(s=e.match(/^rgba?\(([^)]+)\)$/i),s){const r=s[1].split(",").map(n=>n.trim());if(r.length>=3)return`rgba(${r[0]}, ${r[1]}, ${r[2]}, ${t})`}return e}function Jt(h,t){const e=parseFloat(h);return isNaN(e)?t:e}function Ft(h){return h?Array.from(h.querySelectorAll('a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])')).filter(e=>e.getClientRects().length>0):[]}function Tt(h){return h==null||h===""?"(none)":h===!0?"on":h===!1?"off":Array.isArray(h)?h.join(", ")||"(none)":String(h)}function Qt(h,t,e){return h.slice().sort((s,r)=>{const n=t(s),a=t(r);return n==null&&a==null?0:n==null?1:a==null?-1:(na?1:0)*e})}function ft(h,t,e,s,r,n,a){const o=e?s===1?" \u25B2":" \u25BC":' \u2195',i=n==="right"?"text-align:right;":"",l=a?` title="${d(a)}"`:"";return`${h}${o}`}function Mt(h){h=Math.max(0,Math.round(h));const t=Math.floor(h/3600),e=Math.floor(h%3600/60),s=h%60;return t>0?`${t}:${String(e).padStart(2,"0")}:${String(s).padStart(2,"0")}`:`${e}:${String(s).padStart(2,"0")}`}function Zt(h,t){const e={pause:["lbl.artifact_interruption","Interruption"],dip:["lbl.artifact_low_power","Low power"],spike:["lbl.artifact_high_power","High power"]},[s,r]=e[h]||["lbl.artifact_anomaly","Anomaly"];return t?t(s,{},r):r}function te(h){return h.toLowerCase().replace(/[\s&/\-]+/g,"_").replace(/^_+|_+$/g,"").replace(/_+/g,"_")}function ee(h){const t=new Set;return String(h??"").split(",").forEach(e=>{const s=parseInt(e.trim(),10);Number.isFinite(s)&&s>0&&t.add(s)}),Array.from(t).sort((e,s)=>e-s)}function Dt(h,t){if(!h||!h.length)return null;if(t<=h[0][0])return h[0][1];if(t>=h[h.length-1][0])return h[h.length-1][1];for(let e=1;e=t){const s=h[e-1],r=h[e],n=r[0]-s[0]||1;return s[1]+(r[1]-s[1])*((t-s[0])/n)}return h[h.length-1][1]}function _e(h,t,e){e=e||{};const s=h.key,r=h.unit?`${h.label} (${h.unit})`:h.label,n=h.unit?` ${h.unit}`:"",a=h.doc?it(h.doc,h.diagram||ge[s]):"",o=e.changed?``:"";if(h.type==="checkbox")return`
${o}${a}
${h.hint?`
${d(h.hint)}
`:""}
`;let i="";const l=t??"";if(h.type==="select"||h.type==="devicetype"||h.type==="device"){const C=(e.opts||[]).map(([R,B])=>``).join("");i=``}else if(h.type==="textarea")i=``;else if(h.type==="json"){const S=l===""||l==null?"":typeof l=="string"?l:JSON.stringify(l,null,2);i=``}else if(h.type==="checkboxlist"){const S=Array.isArray(t)&&t.length?t.map(String):Array.isArray(h.def)?h.def.map(String):[];i=`
`+(h.choices||[]).map(([C,R])=>``).join("")+"
"}else if(h.type==="entitylist"){const C=(Array.isArray(t)?t:t?[t]:[]).map(R=>`${d(R)}`).join("");i=`
${C}
`}else if(h.type==="timerlist"){const S=Array.isArray(l)?l:[],C=e.t?e.t("lbl.timer_min",{},"min"):"min",R=e.t?e.t("lbl.timer_msg_placeholder",{},"Message (optional, {device}/{program}/{minutes})"):"Message (optional, {device}/{program}/{minutes})",B=e.t?e.t("lbl.timer_auto_pause",{},"Auto-pause"):"Auto-pause",W=e.t?e.t("btn.remove_timer",{},"Delete"):"Delete",O=e.t?e.t("btn.add_timer",{},"+ Add timer"):"+ Add timer",D=(E,M)=>{const H=E&&E.offset_minutes?String(E.offset_minutes):"",q=E&&E.message?d(E.message):"",Q=E&&E.auto_pause?" checked":"";return`
`},A=S.map((E,M)=>D(E,M)).join("");i=`
${A}
`}else if(h.type==="entity"){const S=h.placeholder?` placeholder="${d(h.placeholder)}"`:"";i=`
`}else if(h.type==="list"){const S=Array.isArray(l)?l.join(", "):d(l);i=``}else if(h.type==="intlist"){const S=Array.isArray(l)?l.join(", "):String(l??""),C=h.placeholder?` placeholder="${d(h.placeholder)}"`:"";i=``}else{const S=h.type==="number"?"number":"text",C=e.datalistId?` list="${e.datalistId}"`:"",R=h.step!=null?` step="${h.step}"`:"",B=h.min!=null?` min="${h.min}"`:"",W=h.max!=null?` max="${h.max}"`:"",O=h.placeholder?` placeholder="${d(h.placeholder)}"`:"";i=`${e.datalist||""}`}const c=e.suggestion,_=e.mlSuggestion,p=c&&c.suggested!=null&&!Ot(c.suggested,t)?c.suggested:null,g=_&&_.value!=null&&!Ot(_.value,t)?_.value:null,u=e.t,v=c?c.reason_key?u(c.reason_key,c.reason_params||{},c.reason||""):c.reason||"":"",b=_?_.reason_key?u(_.reason_key,_.reason_params||{},_.reason||""):_.reason||"":"",$=S=>``;let k="";if(p!=null&&g!=null){const S=parseFloat(p),C=parseFloat(g);if((!isNaN(S)&&!isNaN(C)?Math.abs(S-C)/Math.max(Math.abs(S),Math.abs(C),1e-9):1)<.05){const B=u("suggestion.calibrated_label",{},"Calibrated"),W=it([v,b?`${B}: ${b}`:""].filter(Boolean).join(` `));k=`
\u{1F4A1} ${d(u("suggestion.both_agree",{},"WashData recommends"))}${d(p)}${n}${$(p)}${W}
`}else{const B=v?it(v):"",W=b?it(b):"",O=u("suggestion.observed_label",{},"Observed"),D=u("suggestion.calibrated_label",{},"Calibrated");let A="",E="";if(!isNaN(S)&&!isNaN(C)){const q=C>S;E=u(`suggestion.impact.${s}.${q?"higher":"lower"}`,{},""),A=u(`suggestion.impact.${s}.${q?"lower":"higher"}`,{},"")}const M=A?`
${d(A)}
`:"",H=E?`
${d(E)}
`:"";k=`
\u{1F4A1} ${d(O)}${d(p)}${n}${$(p)}${B}${M}
\u{1F916} ${d(D)}${d(g)}${n}${$(g)}${W}${H}
`}}else if(p!=null){const S=v?it(v):"",C=t!=null&&t!==""?` (now ${d(t)}${n})`:"";k=`
\u{1F4A1} ${d(u("suggestion.observed_label",{},"Observed"))}${d(p)}${n}${C}${$(p)}${S}
`}else if(g!=null){const S=b?it(b):"";k=`
\u{1F916} ${d(u("suggestion.calibrated_label",{},"Calibrated"))}${d(g)}${n}${$(g)}${S}
`}if(k&&s){const S=d(u("btn.mute_suggestion",{},"Stop suggesting this setting")),C=``;k=k.replace(/<\/div>\s*$/,C+"")}return`
${o}${a}
${i}${h.hint?`
${d(h.hint)}
`:""}${k}
`}function Ot(h,t){if(h==null||t==null)return!1;const e=parseFloat(h),s=parseFloat(t);return!isNaN(e)&&!isNaN(s)?Math.abs(e-s)<1e-6:String(h)===String(t)}const ge={min_power:"min_power",off_delay:"off_delay",smoothing_window:"smoothing",start_threshold_w:"hysteresis",stop_threshold_w:"hysteresis",start_energy_threshold:"start_energy",running_dead_zone:"dead_zone",profile_duration_tolerance:"duration_tolerance",profile_match_min_duration_ratio:"match_ratios",profile_match_max_duration_ratio:"match_ratios",progress_reset_delay:"progress_reset",completion_min_seconds:"min_duration",min_off_gap:"min_off_gap",start_duration_threshold:"start_duration",end_energy_threshold:"end_energy_thresh",end_repeat_count:"end_repeat",profile_match_threshold:"confidence",profile_unmatch_threshold:"confidence",auto_label_confidence:"confidence",learning_confidence:"confidence",no_update_active_timeout:"watchdog_timeout",anti_wrinkle_enabled:"anti_wrinkle",anti_wrinkle_max_power:"anti_wrinkle",anti_wrinkle_max_duration:"anti_wrinkle",anti_wrinkle_exit_power:"anti_wrinkle",anti_wrinkle_idle_timeout:"anti_wrinkle",sampling_interval:"sampling"};function ue(h){let t=0,e=window.innerWidth;for(let s=h.parentElement;s;s=s.parentElement){if(getComputedStyle(s).overflowX==="visible")continue;const r=s.getBoundingClientRect();r.left>t&&(t=r.left),r.righti${t?fe(t):""}${d(h)}`}function Nt(h,t,e=""){return`${e||""}`}function Et(h,t,e="",s=""){return`
${Nt(h,t,e)}
${s?`
${d(s)}
`:""}
`}function fe(h){const t=s=>`${s}`,e='';switch(h){case"smoothing":return t(`${e} raw vs smoothed`);case"min_power":return t(`${e} minbelow = off`);case"hysteresis":return t(`${e} startstop`);case"start_energy":return t(`${e} spike ignored energy counts`);case"off_delay":return t(`${e} off-delay wait`);case"duration_tolerance":return t(`${e} -tol+tolprofile`);case"match_ratios":return t(`${e} minmax`);case"dead_zone":return t(`${e} ignored`);case"progress_reset":return t(`${e} held at 100%`);case"min_duration":return t(`${e} too short kept`);case"min_off_gap":return t(`${e} off-gap gap below min = one cycle`);case"start_duration":return t(`${e} spike: ignored confirmed`);case"end_energy_thresh":return t(`${e} tail energy above thresh: timer resets accum thr`);case"end_repeat":return t(`${e} R1 R2 R3 N reads below stop = end`);case"confidence":return t(` no match feedback auto 0.0 1.0`);case"watchdog_timeout":return t(`${e} no updates sensor offline: force-stop`);case"anti_wrinkle":return t(`${e} heat phase tumble pulses kept`);case"sampling":return t(`${e} SI SI SI typical reading interval`);default:return""}}class me extends HTMLElement{constructor(){super(),this._hass=null,this._initialized=!1,this._pollTimer=null,this._toastTimer=null,this._hassUpdateThrottle=null,this._evtUnsubs=[],this._hoverRafId=null,this._hoverPending=null,this._constants={stateColors:{},deviceTypes:[],mlLabEnabled:!1,mlSuggestionsEnabled:!1,mlTrainingAvailable:!1,storeOnlineAvailable:!1,storeOnlineEnabled:!1,storeWebOrigin:"",storePrefs:{},pgMatchDefaults:{},version:"",iconUrl:""},this._constantsLoaded=!1,this._devices=[],this._cycles=[],this._refCycles=[],this._shareableCycles=[],this._sharePhasePrograms=[],this._shareAllPrograms=[],this._dlSettings=!1,this._selectMode=!1,this._cycleSel=new Set,this._profiles=[],this._profileGroups={groups:[],suggestions:[],min_cohesion:.85},this._profileEnvCache={},this._suggestions=[],this._lockedSuggestions=[],this._feedbacks=[],this._diag=null,this._phases=[],this._recState=null,this._opts={},this._optDefaults={},this._mlComparison=null,this._mlById={},this._mlLoading=!1,this._mlSettings={},this._mlSettingsLoading=!1,this._mlSettingsByEntry={},this._lockedByEntry={},this._mlTrainingStatus=null,this._setupStatus=null,this._selIdx=0,this._tab="status",this._settingsSec="basic",this._settingsSearch="",this._settingsSugOnly=!1,this._settingsHistoryOpen=!1,this._canvasZoom={},this._toolsSubtab="recording",this._loading=!0,this._tabLoading=!1,this._lastRefresh=null,this._powerHistory=[],this._powerT0=null,this._statusEnv=null,this._statusEnvName=null,this._powerData={live:[],raw:[],cycle_active:!1,cycle_elapsed_s:0},this._stagedSuggestions=!1,this._pendingSettings={},this._busy=new Set,this._tasks={},this._cancellingTasks=new Set,this._taskCallbacks={},this._tasksSubscribed=!1,this._pgHistoryTaskId=null,this._pgSweepTaskId=null,this._panelCfg=null,this._panelTrans=null,this._pollMs=ae,this._panelSubtab="maintenance",this._gearTab="prefs",this._catalog={brands:void 0,devices:void 0,forBrand:null,approvedOnly:!1,brandsFull:!1,brandPrefixes:[]},this._catalogEntry=null,this._maintenance=null,this._logs=[],this._logLevel="",this._logDevice="",this._logComponent="",this._logSearch="";try{this._logOpen=localStorage.getItem("wd-log-open")==="1",this._logDrawerWidth=Math.max(280,parseInt(localStorage.getItem("wd-log-width")||"380",10)||380)}catch{this._logOpen=!1,this._logDrawerWidth=380}this._tabInitialized=!1,this._modal=null,this._prevModal=null,this._toast=null,this._cycleSort={col:"date",dir:-1},this._cycleFilter={text:"",status:""},this._cleanupSort={col:"date",dir:-1},this._profSubtab="profiles",this._statusPhases=[],this._statusPhasesName=null,this._cycleOffset=0,this._cyclesTotal=0,this._cyclesHasMore=!1,this._undoBuffer=new Map,this._undoSeq=0,this._kbdHandler=null,this._tipHandler=null,this._settingsChangelog=null,this._settingsChangeByKey={},this._pgCycleId="",this._pgProfileName="",this._pgPowerPts=null,this._pgDtwData=null,this._pgEnvData=null,this._pgAnalysisTab="history",this._pgDetail=null,this._pgDetailBusy=!1,this._pgHistory=null,this._pgSweepObjective="match_accuracy",this._pgSweepNew=null,this._pgDetailDebounceTimer=null,this._pgThreshStart=null,this._pgThreshStop=null,this._pgParamOverrides={},this._pgEffective=null,this._pgPublishable=null,this._pgPresets=[],this._pgPresetLimit=0,this._pgPresetSel="",this._pgPresetName="",this._pgSuggClassic={},this._pgSuggMl=null,this._pgMlSuggEnabled=!1,this._pgView=null,this._pgHoverT=null,this._pgMap=null,this._pgStressTail=!1,this._pgStressIdleW=null,this._pgPanStart=null,this._pgHoverEvent=null,this._pgBatchProgress=null,this._pgBatchCancel=!1,this._pgLoadSeq=0,this._pgDragging=null,this._pgNeedsRestart=!1,this._pgSimCycles=20,this._pgSweepParam="off_delay",this._pgSweepFrom="",this._pgSweepTo="",this._pgSweepSteps=5,this._pgLoading=!1,this._storeView="brands",this._storeQuery="",this._storeDevices=[],this._storeDevice=null,this._storeProfiles=[],this._storeProfile=null,this._storeCycles=[],this._storeStatus=null,this._storeConnected=!1,this._storeLoading=!1,this._storeConnectListener=null}set hass(t){const e=this._hass;if(this._hass=t,!this._initialized&&t){this._initialized=!0,this._boot();return}e!==t&&this._initialized&&!this._loading&&!this._hassUpdateThrottle&&(this._hassUpdateThrottle=setTimeout(()=>{this._hassUpdateThrottle=null,this._fetchAll()},ne))}set panel(t){this._panel=t}set narrow(t){this._narrow=t}connectedCallback(){this._initialized&&(this._startPoll(),this._setupSubscriptions(),this._fetchAll(),this.shadowRoot&&!this._kbdHandler&&(this._kbdHandler=t=>this._onKeydown(t),this.shadowRoot.addEventListener("keydown",this._kbdHandler)),this.shadowRoot&&!this._tipHandler&&(this._tipHandler=t=>{const e=t.target,s=e&&e.closest?e.closest(".wd-tip"):null;s&&this._positionTip(s)},this.shadowRoot.addEventListener("pointerover",this._tipHandler))),this._onResize=()=>this._resizeLogsPage(),window.addEventListener("resize",this._onResize)}disconnectedCallback(){this._onResize&&(window.removeEventListener("resize",this._onResize),this._onResize=null),this._stopPoll(),this._hassUpdateThrottle&&(clearTimeout(this._hassUpdateThrottle),this._hassUpdateThrottle=null),this._pgRestartRetryTimer&&(clearTimeout(this._pgRestartRetryTimer),this._pgRestartRetryTimer=null),this._evtUnsubs.forEach(t=>{try{t()}catch{}}),this._evtUnsubs=[],this._flushPendingDeletes(),this._kbdHandler&&this.shadowRoot&&(this.shadowRoot.removeEventListener("keydown",this._kbdHandler),this._kbdHandler=null),this._tipHandler&&this.shadowRoot&&(this.shadowRoot.removeEventListener("pointerover",this._tipHandler),this._tipHandler=null),this._storeConnectListener&&(window.removeEventListener("message",this._storeConnectListener),this._storeConnectListener=null)}_boot(){const t=this.attachShadow({mode:"open"}),e=document.createElement("style");e.textContent=oe,t.appendChild(e),this._container=document.createElement("div"),t.appendChild(this._container),this._gtip=document.createElement("div"),this._gtip.className="wd-gtip",t.appendChild(this._gtip),this._kbdHandler=s=>this._onKeydown(s),t.addEventListener("keydown",this._kbdHandler),this._tipHandler=s=>{const r=s.target,n=r&&r.closest?r.closest(".wd-tip"):null;n&&this._positionTip(n)},t.addEventListener("pointerover",this._tipHandler),this._loadPanelTranslations().catch(()=>{}).finally(()=>{this._fetchAll(),this._startPoll()}),this._setupSubscriptions()}_setupSubscriptions(){this._evtUnsubs.forEach(e=>{try{e()}catch{}}),this._evtUnsubs=[],this._tasksSubscribed=!1;const t=this._hass&&this._hass.connection;if(t&&t.subscribeMessage){const e=s=>{const r=s.data||{};if(r.entry_id&&(this._fetchAll(),s.event_type==="ha_washdata_cycle_ended")){const n=this._devices[this._selIdx];n&&n.entry_id===r.entry_id&&this._fetchCycles(r.entry_id).then(()=>{this._tab==="history"&&this._render()})}};for(const s of["ha_washdata_cycle_started","ha_washdata_cycle_ended"])t.subscribeMessage(e,{type:"subscribe_events",event_type:s}).then(r=>{this._evtUnsubs.push(r)}).catch(()=>{});t.subscribeMessage(s=>this._onTaskEvent(s),{type:`${y}/subscribe_tasks`}).then(s=>{this._tasksSubscribed=!0,this._evtUnsubs.push(s)}).catch(()=>{this._tasksSubscribed=!1})}}_onTaskEvent(t){const e=t&&t.task;if(!e||!e.id)return;const s=this._tasks[e.id];s&&(s.updated_at||0)>(e.updated_at||0)||(this._tasks[e.id]=e,e.state!=="running"&&this._cancellingTasks.delete(e.id),this._updateTaskPills(),this._pgAdoptTask(e),this._onTrackedTaskProgress(e),(e.kind==="history_import"||e.kind==="history_import_apply")&&(e.state==="running"?this._modal&&this._modal.type==="history-import"&&this._render():this._histTaskFinished(e)),this._settleTaskCallback(e))}_pgAdoptTask(t){if(t.kind!=="pg_sweep"&&t.kind!=="pg_history")return;const e=this._devices[this._selIdx];if(!e||t.entry_id!==e.entry_id)return;const s=t.kind==="pg_sweep";if(this._pgSweepTaskId||this._pgHistoryTaskId)return;const r=t.state!=="running"&&t.finished_at&&Date.now()/1e3-t.finished_at<30;t.state!=="running"&&!r||(s?this._pgSweepTaskId=t.id:this._pgHistoryTaskId=t.id,this._pgAnalysisTab=s?"sweep":"history",this._busy.add(s?"pg-sweep":"pg-history"),t.state==="running"&&(this._pgBatchProgress={done:t.done||0,total:t.total||0}),this._render())}_pgAdoptExisting(){Object.values(this._tasks||{}).forEach(t=>{this._pgAdoptTask(t),this._onTrackedTaskProgress(t)})}_htmlPgRecentRuns(t){const e=this._devices[this._selIdx];if(!e)return"";const s=Object.values(this._tasks||{}).filter(n=>n.kind===t&&n.entry_id===e.entry_id&&n.state!=="running"&&n.has_result).sort((n,a)=>(a.finished_at||0)-(n.finished_at||0)).slice(0,8);if(!s.length)return"";const r=s.map(n=>{const a=n.finished_at?st(n.finished_at*1e3):"",o=n.state==="cancelled"?" \u26A0":"";return``}).join("");return`
${this._t("lbl.pg_recent_runs",{},"Recent runs")}
${r}
`}_settleTaskCallback(t){if(t.state==="running")return;const e=this._taskCallbacks[t.id];if(e){delete this._taskCallbacks[t.id],e(t);return}this._autoSettleAdopted(t)}_autoSettleAdopted(t){if(t.state!=="done"||t.kind!=="ml_training"&&t.kind!=="reprocess"||!t.finished_at||Date.now()/1e3-t.finished_at>60||(this._autoSettled=this._autoSettled||new Set,this._autoSettled.has(t.id)))return;const e=this._devices[this._selIdx];if(!e||t.entry_id!==e.entry_id)return;this._autoSettled.add(t.id);const s=e.entry_id,r=()=>{this._isActiveEntry(s)&&this._render()};t.kind==="ml_training"?this._loadMlTrainingStatus(s).finally(r):this._fetchToolsData(s).finally(r)}async _kickAndTrack(t,e,s){if(this._busy.has(e))return;this._busy.add(e),this._render();let r;try{const a=await this._ws(t);if(r=a&&a.task_id,!r)throw new Error("no task id");const o=String(t.type||"").endsWith("reprocess_history")?"reprocess":String(t.type||"").endsWith("trigger_ml_training")?"ml_training":String(t.type||"").endsWith("apply_split")?"split":String(t.type||"").endsWith("trim_cycle")?"trim":String(t.type||"").endsWith("apply_merge")?"merge":String(t.type||"").endsWith("rebuild_envelopes")?"rebuild":"task";this._addProvisionalTask(r,o,t.entry_id,0)}catch(a){this._busy.delete(e),this._showToast(this._t("msg.toast_error",{error:a.message||a},"Error: "+(a.message||a)),"error"),this._render();return}this._taskCallbacks[r]=async a=>{if(this._busy.delete(e),a.state==="error"){this._showToast(this._t("msg.toast_error",{error:a.error||""},"Error: "+(a.error||"")),"error"),this._render();return}if(a.state==="cancelled"){this._showToast(this._t("toast.task_cancelled",{},"Cancelled."),"info"),this._render();return}let o=null;try{const i=await this._ws({type:`${y}/get_task_result`,task_id:a.id});o=i&&i.result}catch{}if(o==null){this._showToast(this._t("toast.pg_run_gone",{},"That run is no longer available."),"info"),this._render();return}try{await s(o,a.state)}catch{}this._render()};const n=this._tasks[r];if(n&&n.state!=="running"){this._settleTaskCallback(n);return}this._tasksSubscribed||this._pollTaskGeneric(r)}_finalizeTaskError(t,e){const s=Object.assign({},this._tasks[t]||{id:t},{id:t,state:"error",error:e,finished_at:Date.now()/1e3});this._tasks[t]=s,this._updateTaskPills(),this._settleTaskCallback(s)}async _pollTaskGeneric(t){let e=0;for(let s=0;s<3600&&this._taskCallbacks[t];s++){let r;try{r=await this._ws({type:`${y}/get_task_result`,task_id:t}),e=0}catch{if(++e>=5){this._finalizeTaskError(t,"lost connection");return}await new Promise(a=>setTimeout(a,1200));continue}if(!r){this._finalizeTaskError(t,"no result");return}if(this._tasks[t]=r,this._updateTaskPills(),r.state!=="running"){this._settleTaskCallback(r);return}await new Promise(n=>setTimeout(n,1200))}this._taskCallbacks[t]&&this._finalizeTaskError(t,"timed out")}_deviceName(t){const e=(this._devices||[]).find(s=>s.entry_id===t);return e&&(e.name||e.title)||""}_taskActionLabel(t){return{pg_history:this._t("lbl.task_pg_history",{},"Test on history"),pg_sweep:this._t("lbl.task_pg_sweep",{},"Optimize"),pg_detail:this._t("lbl.task_pg_detail",{},"Simulate cycle"),split:this._t("lbl.task_split",{},"Splitting cycle"),trim:this._t("lbl.task_trim",{},"Trimming cycle"),merge:this._t("lbl.task_merge",{},"Merging cycles"),rebuild:this._t("lbl.task_rebuild",{},"Rebuilding envelopes"),reprocess:this._t("lbl.task_reprocess",{},"Reprocessing"),ml_training:this._t("lbl.task_ml_training",{},"Learning"),history_import:this._t("lbl.task_history_import",{},"Scanning power history"),history_import_apply:this._t("lbl.task_history_import_apply",{},"Importing cycles")}[t]||t}_fmtEta(t){return t=Math.round(t),t<60?this._t("lbl.eta_secs",{n:t},`~${t}s left`):this._t("lbl.eta_mins",{n:Math.round(t/60)},`~${Math.round(t/60)}m left`)}_exclNote(t){if(!t||!t.total)return"";const e=(t.items||[]).map(([s,r])=>`${r} ${this._t("suggestion.exclusions.reason."+s,{},String(s).replace(/_/g," "))}`).join(", ");return" "+this._t("suggestion.exclusions.summary",{total:t.total,parts:e},`Excluded ${t.total} mis-detected cycle(s): ${e}.`)}_htmlTaskPills(){const t=Object.values(this._tasks||{}).filter(s=>s.state==="running"),e=new Set(t.map(s=>s.id));return this._cancellingTasks.forEach(s=>{e.has(s)||this._cancellingTasks.delete(s)}),t.length?t.map(s=>{const r=this._cancellingTasks.has(s.id),n=this._deviceName(s.entry_id),a=s.label_key?this._t(s.label_key,s.label_params||{},s.label||this._taskActionLabel(s.kind)):this._taskActionLabel(s.kind),o=(n?n+" \xB7 ":"")+a,i=s.progress!=null?Math.round(s.progress*100)+"%":"",l=s.eta_s!=null&&s.eta_s>0?this._fmtEta(s.eta_s):"",c=this._t("task.cancelling",{},"Cancelling\u2026"),_="wd-task-pill"+(r?" wd-task-pill--cancelling":""),p=r?" \xB7 "+c:i?" "+i:"";return`${d(o)}`+(r?`${d(c)}`:(i?`${i}`:"")+(l?`${d(l)}`:""))+``}).join(""):""}_updateTaskPills(){const t=this.shadowRoot;if(!t)return;const e=t.getElementById("wd-task-pills");e&&(e.innerHTML=this._htmlTaskPills())}_addProvisionalTask(t,e,s,r){!t||this._tasks[t]||(this._tasks[t]={id:t,entry_id:s,kind:e,label:this._taskActionLabel(e),state:"running",done:0,total:r||0,progress:r?0:null,eta_s:null,updated_at:0,has_result:!1},this._updateTaskPills())}_onTrackedTaskProgress(t){if(!(t.id!==this._pgHistoryTaskId&&t.id!==this._pgSweepTaskId)){if(t.state==="running"){this._pgBatchProgress={done:t.done||0,total:t.total||0},this._pgUpdateBatchBar(t.done||0,t.total||0);return}this._pgFinishTask(t,t.id===this._pgHistoryTaskId)}}async _pgFinishTask(t,e){let s=null;try{if(t.state==="done"||t.state==="cancelled"){const r=await this._ws({type:`${y}/get_task_result`,task_id:t.id});s=r&&r.result}}catch{}this._isActiveEntry(t.entry_id)&&(t.state==="error"?this._showToast(this._t("msg.toast_error",{error:t.error||""},"Error: "+(t.error||"")),"error"):s&&(e?this._pgHistory=s:this._pgSweepNew=s&&!s.error?s:null),e?(this._busy.delete("pg-history"),this._pgHistoryTaskId=null):(this._busy.delete("pg-sweep"),this._pgSweepTaskId=null),this._pgBatchProgress=null,this._render())}async _pgPollTask(t){for(let e=0;e<3600&&(t===this._pgHistoryTaskId||t===this._pgSweepTaskId);e++){let s;try{s=await this._ws({type:`${y}/get_task_result`,task_id:t})}catch{break}if(!s)break;if(this._tasks[t]=s,this._updateTaskPills(),s.state!=="running"){this._onTrackedTaskProgress(s);return}this._pgBatchProgress={done:s.done||0,total:s.total||0},this._pgUpdateBatchBar(s.done||0,s.total||0),await new Promise(r=>setTimeout(r,1200))}}_panelTransUrl(t){const e=`/ha_washdata/panel-translations/${encodeURIComponent(t)}.json`;return Ht?`${e}?v=${encodeURIComponent(Ht)}`:e}async _fetchPanelLang(t){if(!t)return null;const e=[t],s=t.indexOf("-");s>0&&e.push(t.slice(0,s));for(const r of e)try{const n=await fetch(this._panelTransUrl(r));if(n.ok){const a=await n.json();if(a&&typeof a=="object")return a}}catch{}return null}async _loadPanelLang(t){if(!t||this._panelTrans&&this._panelTrans[t])return;const e=await this._fetchPanelLang(t);e&&(this._panelTrans||(this._panelTrans={}),this._panelTrans[t]=e)}async _loadPanelTranslations(){const t=this._hass&&this._hass.locale&&this._hass.locale.language;await Promise.all([this._loadPanelLang("en"),t&&t!=="en"?this._loadPanelLang(t):Promise.resolve()])}_startPoll(){this._stopPoll(),this._pollTimer=setInterval(()=>this._fetchAll(),this._pollMs)}_stopPoll(){this._pollTimer&&(clearInterval(this._pollTimer),this._pollTimer=null)}async _ws(t){return this._hass.connection.sendMessagePromise(t)}async _fetchAll(){if(!this._hass)return;const t=this._loading;try{if(!this._constantsLoaded){try{const r=await this._ws({type:`${y}/get_constants`});this._constants={stateColors:r.state_colors||{},deviceTypes:r.device_types||[],mlLabEnabled:!!r.ml_lab_enabled,mlSuggestionsEnabled:!!r.ml_suggestions_enabled,mlTrainingAvailable:!!r.ml_training_available,storeOnlineAvailable:!!r.store_online_available,storeOnlineEnabled:!!r.store_online_enabled,storeWebOrigin:r.store_web_origin||"",storePrefs:r.store_prefs||{},pgMatchDefaults:r.pg_match_defaults||{},PROFILE_MIN_WARMUP_CYCLES:r.PROFILE_MIN_WARMUP_CYCLES,version:r.version||"",iconUrl:r.icon_url||""}}catch{}try{this._panelCfg=await this._ws({type:`${y}/get_panel_config`}),this._applyPanelConfig()}catch{}this._constantsLoaded=!0}const e=await this._ws({type:`${y}/get_devices`});if(this._lastFetchErr=null,this._devices=e.devices||[],this._lastRefresh=new Date,this._selIdx===0&&this._devices.length>1){const r=localStorage.getItem("wd-last-device");if(r){const n=this._devices.findIndex(a=>a.entry_id===r);n>0&&(this._selIdx=n)}}const s=this._devices[this._selIdx];if(s&&this._tab==="status"){try{this._powerData=await this._ws({type:`${y}/get_power_history`,entry_id:s.entry_id,with_raw:this._pref("show_raw_active",!1)})}catch{}if(this._pref("show_debug",!1))try{this._matchDebug=await this._ws({type:`${y}/get_match_debug`,entry_id:s.entry_id})}catch{}if(this._canEdit()&&s.recording)try{this._recState=await this._ws({type:`${y}/get_recording_state`,entry_id:s.entry_id})}catch{}}if(s&&s.current_program){if(this._statusEnvName!==s.current_program){this._statusEnvName=s.current_program;try{const r=await this._ws({type:`${y}/get_profile_envelope`,entry_id:s.entry_id,profile_name:s.current_program});this._statusEnv=r.envelope||null}catch{this._statusEnv=null}}await this._ensureStatusPhases(s.entry_id,s.current_program)}else this._statusEnv=null,this._statusEnvName=null,this._statusPhases=[],this._statusPhasesName=null;if(t&&s){if(await this._fetchCycles(s.entry_id),await this._fetchSuggestions(s.entry_id),await this._fetchProfiles(s.entry_id),this._tab==="status")try{this._setupStatus=await this._ws({type:`${y}/get_setup_status`,entry_id:s.entry_id})}catch{this._setupStatus=null}if(this._constants.storeOnlineAvailable){try{const r=await this._ws({type:`${y}/get_options`,entry_id:s.entry_id});this._isActiveEntry(s.entry_id)&&(this._opts=r.options||{})}catch{}this._onlineEnabled()&&await this._loadStoreStatus(s.entry_id)}}this._logOpen&&this._isAdmin()&&this._fetchLogs().then(()=>this._refreshLogDrawer()).catch(()=>{})}catch(e){const s=Pt(e);s!==this._lastFetchErr&&(this._lastFetchErr=s,console.warn("[WashData panel] fetch error -",s,e))}finally{this._loading=!1;const e=this.shadowRoot,s=e&&e.activeElement,r=!!(s&&["SELECT","INPUT","TEXTAREA","OPTION"].includes(s.tagName));t?this._render():this._tab==="status"&&!this._modal&&!r?this._render():this._tab==="status"&&!this._modal&&r?(this._drawStatusCurve(),this._refreshDeviceBar()):this._refreshDeviceBar()}}async _fetchCycles(t){this._cyclesError=!1;try{const e=await this._ws({type:`${y}/get_device_cycles`,entry_id:t,limit:Rt,offset:0});this._cycles=e.cycles||[],this._refCycles=[...e.reference_cycles||[],...e.backfill_cycles||[]],this._cycleOffset=this._cycles.length,this._cyclesTotal=e.total!=null?e.total:this._cycles.length,this._cyclesHasMore=e.has_more!=null?!!e.has_more:!1}catch{this._cyclesError=!0,this._cycles=[],this._refCycles=[],this._cycleOffset=0,this._cyclesTotal=0,this._cyclesHasMore=!1}}async _loadMoreCycles(t){const e=await this._ws({type:`${y}/get_device_cycles`,entry_id:t,limit:Rt,offset:this._cycleOffset}),s=e.cycles||[],r=new Set(this._cycles.map(n=>n.id));for(const n of s)r.has(n.id)||this._cycles.push(n);this._cycleOffset+=s.length,this._cyclesTotal=e.total!=null?e.total:this._cyclesTotal,this._cyclesHasMore=e.has_more!=null?!!e.has_more:s.length>=Rt}async _ensureStatusPhases(t,e){if(!e){this._statusPhases=[],this._statusPhasesName=null;return}if(this._statusPhasesName!==e){this._statusPhasesName=e;try{const s=await this._ws({type:`${y}/get_profile_phases`,entry_id:t,profile_name:e});this._statusPhases=(s.phases||[]).map(r=>({name:r.name,start:r.start,end:r.end}))}catch{this._statusPhases=[]}}}async _fetchSettingsChangelog(t){try{const s=await this._ws({type:`${y}/get_settings_changelog`,entry_id:t});this._settingsChangelog=s.changelog||[]}catch{this._settingsChangelog=this._settingsChangelog||[]}const e={};for(const s of this._settingsChangelog||[])s&&s.key!=null&&!(s.key in e)&&(e[s.key]=s);this._settingsChangeByKey=e}_registerUndo(t){const e="u"+ ++this._undoSeq;return t.timer=setTimeout(()=>this._commitDelete(e),1e4),this._undoBuffer.set(e,t),e}_undoDelete(t){const e=this._undoBuffer.get(t);if(e){this._undoBuffer.delete(t),e.timer&&clearTimeout(e.timer);try{e.restore()}catch{}this._toastTimer&&clearTimeout(this._toastTimer),this._toast=null,this._render()}}async _commitDelete(t){const e=this._undoBuffer.get(t);if(!e)return;this._undoBuffer.delete(t),e.timer&&clearTimeout(e.timer);const s=(r,n)=>{const a=this._devices[this._selIdx],o=a&&a.entry_id;if(e.eid===o){try{e.restore(r)}catch{}this._showToast(n,"error"),this._render()}};try{const r=await e.commit();r&&r.length&&s(r,this._t("toast.delete_partial_failed",{},"Some items could not be deleted and were restored"))}catch(r){s(null,this._t("toast.delete_failed",{error:r&&r.message||r},"Delete failed: "+(r&&r.message||r)))}}_isActiveEntry(t){const e=this._devices[this._selIdx];return!!e&&e.entry_id===t}_flushPendingDeletes(){return!this._undoBuffer||!this._undoBuffer.size?Promise.resolve():Promise.all(Array.from(this._undoBuffer.keys()).map(t=>this._commitDelete(t)))}_deleteCyclesWithUndo(t,e){const s=new Set(e),r=[];if(this._cycles=(this._cycles||[]).filter((i,l)=>s.has(i.id)?(r.push({idx:l,rec:i,ref:!1}),!1):!0),this._refCycles=(this._refCycles||[]).filter((i,l)=>s.has(i.id)?(r.push({idx:l,rec:i,ref:!0}),!1):!0),!r.length)return;this._cycleSel.clear(),this._selectMode=!1,this._render();const n=i=>{const l=i&&i.length?i:r,c=this._cycles.slice(),_=this._refCycles.slice();l.slice().sort((p,g)=>p.idx-g.idx).forEach(({idx:p,rec:g,ref:u})=>{const v=u?_:c;v.splice(Math.min(p,v.length),0,g)}),this._cycles=c,this._refCycles=_},a=async()=>{const i=[];for(const l of r)try{await this._ws({type:`${y}/delete_cycle`,entry_id:t,cycle_id:l.rec.id})}catch{i.push(l)}if(i.lengthi.name===e),r=s>=0?this._profiles[s]:{name:e};s>=0&&(this._profiles=this._profiles.filter(i=>i.name!==e)),this._modal=null,this._render();const n=i=>{const l=this._profiles.slice();l.splice(Math.min(s<0?l.length:s,l.length),0,r),this._profiles=l},a=async()=>{try{await this._ws({type:`${y}/delete_profile`,entry_id:t,profile_name:e,unlabel_cycles:!0})}catch{return[{idx:s,rec:r}]}try{await this._fetchProfiles(t),this._tab==="profiles"&&this._render()}catch{}return[]},o=this._registerUndo({eid:t,restore:n,commit:a});this._showToast(this._t("msg.profile_deleted",{name:e},"Profile deleted"),"success",{actionLabel:this._t("btn.undo",{},"Undo"),actionToken:o,duration:1e4})}_onKeydown(t){if(!t.defaultPrevented){if(t.key==="Escape"){this._modal&&(t.preventDefault(),this._onModalAction("cancel",null));return}if(this._modal&&t.key==="Tab"){const e=this.shadowRoot,s=e&&e.querySelector('.wd-modal[role="dialog"]');if(s){const r=Ft(s);if(r.length){const n=e.activeElement,a=r.indexOf(n);t.shiftKey?a<=0&&(t.preventDefault(),r[r.length-1].focus()):(a===-1||a===r.length-1)&&(t.preventDefault(),r[0].focus())}}return}if(["ArrowLeft","ArrowRight","Home","End"].includes(t.key)){const e=this.shadowRoot,s=e&&e.activeElement;if(s&&s.classList.contains("wd-tab")){const r=[...e.querySelectorAll("button.wd-tab")],n=r.indexOf(s);if(n===-1)return;let a=n;t.key==="ArrowRight"?a=(n+1)%r.length:t.key==="ArrowLeft"?a=(n-1+r.length)%r.length:t.key==="Home"?a=0:t.key==="End"&&(a=r.length-1),t.preventDefault(),r[a].click(),r[a].focus()}}}}async _loadMlIndex(t){if(this._mlById=this._mlById||{},!!this._constants.mlLabEnabled)try{const e=await this._ws({type:`${y}/get_ml_comparison`,entry_id:t});if(!this._isActiveEntry(t))return;this._mlComparison=e;const s={};for(const r of e&&e.cycles||[])s[r.id]=r;this._mlById=s,this._mlSettings=e&&e.settings_comparison||this._mlSettings,this._mlSettingsByEntry[t]=this._mlSettings}catch{}}async _loadMlSettings(t){if(this._mlSettings=this._mlSettings||{},!!this._constants.mlSuggestionsEnabled)try{const e=this._mlComparison||await this._ws({type:`${y}/get_ml_comparison`,entry_id:t});if(!this._isActiveEntry(t))return;this._mlComparison=e,this._mlSettings=e&&e.settings_comparison||{},this._mlSettingsByEntry[t]=this._mlSettings}catch{}}async _loadMlTrainingStatus(t){if(this._constants.mlTrainingAvailable)try{const e=await this._ws({type:`${y}/get_ml_training_status`,entry_id:t});if(!this._isActiveEntry(t))return;this._mlTrainingStatus=e}catch{}}async _fetchCycleProfileEnv(t,e){if(e)try{const s=await this._ws({type:`${y}/get_profile_envelope`,entry_id:t,profile_name:e}),r=this._modal;r&&r.type==="cycle-detail"&&r.entryId===t&&r.curve&&(r.curve.profile_name||"")===e&&(r.profileEnv=s.envelope||null,this._render())}catch{}}async _fetchSuggestions(t){this._suggestionsError=!1;try{const e=await this._ws({type:`${y}/get_suggestions`,entry_id:t});if(!this._isActiveEntry(t))return;this._suggestions=e.suggestions||[],this._lockedSuggestions=e.locked_suggestions||[],this._lockedByEntry[t]=this._lockedSuggestions}catch{this._isActiveEntry(t)&&(this._suggestionsError=!0,this._suggestions=[])}}async _fetchProfiles(t){this._profilesError=!1;try{const e=await this._ws({type:`${y}/get_profiles`,entry_id:t});this._profiles=e.profiles||[],this._profileHealth=e.profile_health||{},this._profileTrends=e.profile_trends||{},this._coverageGaps=e.coverage_gaps||{},this._profileAdvisories=e.profile_advisories||[]}catch{this._profilesError=!0}return this._profiles}async _ensureProfileEnvs(t,e){this._profileEnvCache=this._profileEnvCache||{};const s=[...new Set(e)].filter(r=>r&&!(r in this._profileEnvCache));return s.length?(await Promise.all(s.map(async r=>{try{const n=await this._ws({type:`${y}/get_profile_envelope`,entry_id:t,profile_name:r});this._profileEnvCache[r]=n&&n.envelope||null}catch{this._profileEnvCache[r]=null}})),this._profileEnvCache):this._profileEnvCache}async _fetchProfileGroups(t){this._profileGroupsError=!1;try{const e=await this._ws({type:`${y}/get_profile_groups`,entry_id:t});this._profileGroups={groups:e.groups||[],suggestions:e.suggestions||[],min_cohesion:e.min_cohesion||.85}}catch{this._profileGroupsError=!0,this._profileGroups={groups:[],suggestions:[],min_cohesion:.85}}return this._profileGroups}async _selectDevice(t){if(t===this._selIdx)return;await this._flushPendingDeletes(),this._selIdx=t;const e=this._devices[t];e&&localStorage.setItem("wd-last-device",e.entry_id),this._pendingSettings={},this._prevOpts=null,this._cascadePending={},this._preCascadeOpts=null,this._stagedSuggestions=!1,this._mlComparison=null,this._mlById={},this._mlSettings={},this._profileEnvCache={},this._powerHistory=[],this._powerT0=null,this._statusEnv=null,this._statusEnvName=null,this._statusPhases=[],this._statusPhasesName=null,this._cycleOffset=0,this._cyclesTotal=0,this._cyclesHasMore=!1,this._settingsChangelog=null,this._settingsChangeByKey={},this._powerData={live:[],raw:[],cycle_active:!1,cycle_elapsed_s:0},this._matchDebug=null,this._profiles=[],this._profileHealth={},this._profileTrends={},this._coverageGaps={},this._profileAdvisories=[],this._opts={},this._optDefaults={},this._suggestions=[],this._lockedSuggestions=[],this._cycles=[],this._refCycles=[],this._recState=null,this._diag=null,this._maintenance=null,this._phases=[],this._mlTrainingStatus=null,this._setupStatus=null,this._deviceAutomations=[],this._selectMode=!1,this._cycleSel=new Set,this._cycleFilter={text:"",status:""},this._profSubtab="profiles",this._pgCycleId="",this._pgProfileName="",this._pgPowerPts=null,this._pgDtwData=null,this._pgEnvData=null,this._pgThreshStart=null,this._pgThreshStop=null,this._pgParamOverrides={},this._pgEffective=null,this._pgPublishable=null,this._pgPresets=[],this._pgPresetSel="",this._pgPresetName="",this._pgPresetLimit=0,this._pgSuggClassic={},this._pgSuggMl=null,this._pgMlSuggEnabled=!1,this._pgView=null,this._pgHoverT=null,this._pgLoadSeq++,this._pgNeedsRestart=!1,this._pgLoading=!1,this._pgDetail=null,this._pgHistory=null,this._pgSweepNew=null,this._pgHistoryTaskId=null,this._pgSweepTaskId=null,this._busy.delete("pg-history"),this._busy.delete("pg-sweep"),this._pgBatchProgress=null,this._pgDetailDebounceTimer&&(clearTimeout(this._pgDetailDebounceTimer),this._pgDetailDebounceTimer=null),this._pgStressTail=!1,this._pgStressIdleW=null,this._storeView="brands",this._storeDevice=null,this._storeProfile=null,this._storeQuery="",this._storeDevices=[],this._storeProfiles=[],this._storeCycles=[],this._storeStatus=null,this._storeConnected=!1,this._storeLoading=!1,clearTimeout(this._brandSearchTimer),this._brandSearchTimer=null,this._catalog={brands:this._catalog.brands,devices:void 0,forBrand:null,approvedOnly:this._catalog.approvedOnly,brandsFull:this._catalog.brandsFull,brandPrefixes:this._catalog.brandPrefixes},this._entityListCache&&delete this._entityListCache.store_model;const s=this._devices[this._selIdx];s&&await this._fetchSuggestions(s.entry_id),this._fetchTabData()}_refreshDeviceBar(){const t=this.shadowRoot;if(!t)return;const e=t.querySelector(".wd-devbar"),s=this._htmlDeviceBar();if(e&&s){const r=document.createElement("div");r.innerHTML=s;const n=r.firstElementChild;n&&(e.replaceWith(n),n.querySelectorAll(".wd-devcard[data-idx]").forEach(a=>a.addEventListener("click",()=>this._selectDevice(parseInt(a.dataset.idx,10)))))}}_refreshLogDrawer(){this._logOpen&&(this._refreshLogViews(),this._refreshLogFilterOptions())}_refreshLogFilterOptions(){const t=this.shadowRoot;if(!t)return;const e=(n,a,o)=>``+a.map(i=>``).join(""),s=e(this._t("log.all_devices",{},"All devices"),this._logDevices(),this._logDevice),r=e(this._t("log.all_components",{},"All components"),this._logComponents(),this._logComponent);t.querySelectorAll('.wd-log-filter[data-logfilter="device"]').forEach(n=>{n.innerHTML=s,n.value=this._logDevice||""}),t.querySelectorAll('.wd-log-filter[data-logfilter="component"]').forEach(n=>{n.innerHTML=r,n.value=this._logComponent||""})}async _fetchTabData(){const t=this._devices[this._selIdx];if(!t)return;const e=t.entry_id;this._tabLoading=!0,this._render();try{if(this._tab==="status"){if(this._powerData=await this._ws({type:`${y}/get_power_history`,entry_id:e,with_raw:this._pref("show_raw_active",!1)}),this._profiles.length||await this._fetchProfiles(e),!this._profiles.length&&!this._cycles.length&&await this._fetchCycles(e),await this._ensureStatusPhases(e,t.current_program),this._pref("show_debug",!1))try{this._matchDebug=await this._ws({type:`${y}/get_match_debug`,entry_id:e})}catch{}if(this._canEdit())try{this._recState=await this._ws({type:`${y}/get_recording_state`,entry_id:e})}catch{}try{this._setupStatus=await this._ws({type:`${y}/get_setup_status`,entry_id:e})}catch{this._setupStatus=null}}else if(this._tab==="history"){await this._fetchCycles(e),this._profiles.length||await this._fetchProfiles(e);try{const s=await this._ws({type:`${y}/get_feedbacks`,entry_id:e});this._feedbacks=s.feedbacks||[]}catch{}this._constants.storeOnlineAvailable&&this._onlineEnabled()&&this._loadStoreStatus(e).then(()=>{this._tab==="history"&&this._render()}),this._constants.mlLabEnabled&&(this._mlLoading=!0,this._loadMlIndex(e).finally(()=>{this._mlLoading=!1;const s=this._modal;s&&s.type==="cycle-detail"&&!s.ml&&this._mlById[s.cycleId]&&(s.ml=this._mlById[s.cycleId]),(this._tab==="history"||s&&s.type==="cycle-detail")&&this._render()}))}else if(this._tab==="profiles"){if(await this._fetchProfiles(e),this._fetchProfileGroups(e).then(()=>{this._tab==="profiles"&&this._render()}),this._cycles.length||this._fetchCycles(e).then(()=>{this._tab==="profiles"&&this._render()}),this._profSubtab==="phase-catalog")try{const s=await this._ws({type:`${y}/get_phase_catalog`,entry_id:e});this._phases=s.phases||[]}catch{}}else if(this._tab==="settings"){const s=await this._ws({type:`${y}/get_options`,entry_id:e});if(!this._isActiveEntry(e))return;this._opts=s.options||{},this._optDefaults=s.defaults||{},await this._fetchSuggestions(e),await this._fetchSettingsChangelog(e),this._constants.mlSuggestionsEnabled&&(this._mlSettingsLoading=!0,this._loadMlSettings(e).finally(()=>{this._mlSettingsLoading=!1,this._tab==="settings"&&this._renderPreservingFormEdits()})),this._constants.mlTrainingAvailable&&this._loadMlTrainingStatus(e).finally(()=>{this._tab==="settings"&&this._renderPreservingFormEdits()}),this._autoLoading=!0,this._loadDeviceAutomations(e).finally(()=>{this._autoLoading=!1,this._tab==="settings"&&this._renderPreservingFormEdits()}),this._constants.storeOnlineAvailable&&this._loadStoreStatus(e).finally(()=>{this._tab==="settings"&&this._renderPreservingFormEdits()})}else if(this._tab==="store"){try{const s=await this._ws({type:`${y}/get_options`,entry_id:e});if(!this._isActiveEntry(e))return;this._opts=s.options||{}}catch{}if(this._profiles.length||this._fetchProfiles(e),await this._loadStoreStatus(e),!this._isActiveEntry(e))return;this._ensureStoreConnectListener(),this._onlineEnabled()&&this._storeSearch(this._storeBrandScope())}else if(this._tab==="advanced"&&this._panelSubtab==="ml"){const s=await this._ws({type:`${y}/get_options`,entry_id:e});if(!this._isActiveEntry(e))return;this._opts=s.options||{},this._loadMlTrainingStatus(e).finally(()=>{this._tab==="advanced"&&this._panelSubtab==="ml"&&this._renderPreservingFormEdits()})}else if(this._tab==="playground"){if(await Promise.all([(async()=>{try{const s=await this._ws({type:`${y}/get_options`,entry_id:e});this._isActiveEntry(e)&&(this._opts=s.options||{})}catch{}})(),this._pgFetchSettings(e,!1),this._fetchCycles(e),this._profiles.length?Promise.resolve():this._fetchProfiles(e)]),!this._isActiveEntry(e))return;this._pgFetchSuggestions(e),!this._pgCycleId&&this._cycles?.length&&(this._pgCycleId=this._cycles[0].id,this._pgProfileName=""),this._pgNeedsRestart=!1,this._pgRestartRetries=0,this._pgAdoptExisting()}else this._tab==="advanced"&&this._panelSubtab==="maintenance"&&!this._maintenance&&this._fetchMaintenance(e).then(()=>{this._tab==="advanced"&&this._render()})}catch(s){console.warn("[WashData panel] tab data fetch error -",Pt(s),s)}finally{this._tabLoading=!1,this._render()}}async _fetchToolsData(t){try{const e=await this._ws({type:`${y}/get_diagnostics`,entry_id:t});if(!this._isActiveEntry(t))return;this._diag=e.stats||{}}catch(e){console.warn("[WashData panel] tools fetch error -",Pt(e),e),this._diag={_error:String(e&&e.message||e)}}}async _fetchMaintenance(t){try{const e=await this._ws({type:`${y}/get_maintenance_log`,entry_id:t});this._maintenance=e||{}}catch(e){console.warn("[WashData panel] maintenance fetch error -",Pt(e),e),this._maintenance={_error:String(e&&e.message||e)}}}async _fetchLogs(){try{const t=await this._ws({type:`${y}/get_logs`,level:null,limit:500});this._logs=t.logs||[]}catch(t){console.warn("[WashData panel] logs fetch error -",Pt(t),t)}}_logComponents(){return[...new Set((this._logs||[]).map(t=>t.logger).filter(Boolean))].sort()}_logDevices(){return[...new Set((this._logs||[]).map(t=>t.device).filter(Boolean))].sort()}_filteredLogRecords(){const t={DEBUG:10,INFO:20,WARNING:30,ERROR:40,CRITICAL:50},e=this._logLevel&&t[this._logLevel]||0,s=this._logDevice,r=this._logComponent,n=(this._logSearch||"").trim().toLowerCase();return(this._logs||[]).filter(a=>!(e&&(t[a.level]||0){const s=new Date(e.ts*1e3).toLocaleTimeString(),r=e.device?`${d(e.device)}`:"";return`
${s}${d(e.level)}${d(e.logger||"")}${r}${d(e.msg)}
`}).join(""):(this._logs||[]).length>0?`

${this._t("msg.no_logs_match",{},"No log records match these filters.")}

`:`

${this._t("msg.no_logs",{},"No log records buffered yet.")}

`}_htmlLogFilters(t){const s=["","DEBUG","INFO","WARNING","ERROR"].map(a=>``).join(""),r=``+this._logDevices().map(a=>``).join(""),n=``+this._logComponents().map(a=>``).join("");return` `}_refreshLogViews(){const t=this.shadowRoot;if(!t)return;const e=this._logLinesHtml();["wd-log-lines-drawer","wd-log-lines-page"].forEach(s=>{const r=t.getElementById(s);r&&(r.innerHTML=e)})}_syncLogFilters(t){const e=this.shadowRoot;e&&e.querySelectorAll(`.wd-log-filter[data-logfilter="${t.dataset.logfilter}"]`).forEach(s=>{s!==t&&s.value!==t.value&&(s.value=t.value)})}async _fetchRecState(t){try{this._recState=await this._ws({type:`${y}/get_recording_state`,entry_id:t})}catch{}}async _fetchFeedbacks(t){try{const e=await this._ws({type:`${y}/get_feedbacks`,entry_id:t});this._feedbacks=e.feedbacks||[]}catch{}}async _fetchPhases(t){try{const e=await this._ws({type:`${y}/get_phase_catalog`,entry_id:t});this._phases=e.phases||[]}catch{}}_localize(t,e){try{const s=this._hass&&this._hass.localize?this._hass.localize(t):"";return s&&s!==t?s:e}catch{return e}}_tLookup(t,e){const s=this._panelTrans&&(this._panelTrans[e]||this._panelTrans.en);if(!s)return null;const r=t.split(".").reduce((n,a)=>n&&n[a]!==void 0?n[a]:null,s);return r&&typeof r=="string"?r:null}_t(t,e={},s=""){let r;const a=this._panelCfg&&this._panelCfg.prefs&&this._panelCfg.prefs.lang_override||this._hass&&this._hass.locale&&this._hass.locale.language;this._panelTrans?r=a&&this._tLookup(t,a)||this._tLookup(t,"en")||s:r=this._localize(`component.${y}.panel.${t}`,s);for(const[o,i]of Object.entries(e))r=r.replace(new RegExp(`\\{${o}\\}`,"g"),String(i));return r}_stateColor(t){const e=this._constants.stateColors||{};return e[t]||e.unknown||"var(--disabled-color, #bdbdbd)"}_stateLabel(t){const e=(t||"unknown").replace(/_/g," ").replace(/\b\w/g,s=>s.toUpperCase());return this._localize(`component.${y}.entity.sensor.washer_state.state.${t}`,e)}_deviceTypeLabel(t){const e=(this._constants.deviceTypes||[]).find(r=>r.id===t),s=e?e.label:(t||"").replace(/_/g," ");return this._localize(`component.${y}.selector.device_type.options.${t}`,s)}_deviceTypeOpts(t){return(this._constants.deviceTypes||[]).map(e=>[e.id,this._deviceTypeLabel(e.id)])}_deviceOpts(){const t=[["","- None -"]],e=this._hass&&this._hass.devices?this._hass.devices:{};return Object.values(e).forEach(s=>{const r=s.name_by_user||s.name||s.id;t.push([s.id,r])}),t}_applyFontScale(t){let e=parseFloat(t);isFinite(e)||(e=1),e=Math.max(.7,Math.min(2,e)),this.style.fontSize=e===1?"":Math.round(e*1e3)/10+"%"}_applyPanelConfig(){const t=this._panelCfg;if(!t)return;this._applyFontScale(t.prefs&&t.prefs.font_scale||1);const e=t.prefs&&t.prefs.lang_override;e&&!(this._panelTrans&&this._panelTrans[e])&&this._loadPanelLang(e).then(()=>this._render()).catch(()=>{});const s=t.panel||{};if(!this._tabInitialized){const r=t.prefs&&t.prefs.default_tab||s.default_tab;r&&["status","history","profiles","settings","playground"].includes(r)&&(this._tab=r),this._tabInitialized=!0}}_isAdmin(){return!!(this._panelCfg&&this._panelCfg.is_admin)}_curPerm(){const t=this._devices[this._selIdx];return t&&t.perm||"full"}_canEdit(){const t=this._curPerm();return this._isAdmin()||t==="edit"||t==="full"}_canFull(){const t=this._curPerm();return this._isAdmin()||t==="full"}_onlineEnabled(){return!!(this._constants&&this._constants.storeOnlineAvailable&&this._constants.storeOnlineEnabled)}_visibleTabIds(){const t=this._isAdmin(),e=!t&&this._panelCfg&&this._panelCfg.panel&&this._panelCfg.panel.hidden_tabs||[],s=["status","history","profiles"];return this._canEdit()&&s.push("settings"),this._canEdit()&&s.push("playground"),this._canEdit()&&this._onlineEnabled()&&s.push("store"),s.push("advanced"),s.filter(r=>t||!e.includes(r))}async _busyRun(t,e){this._busy.add(t),this._render();try{return await e()}finally{this._busy.delete(t),this._render()}}async _closeCycleDetail(t){const e=this._prevModal;if(this._prevModal=null,e&&e.type==="profile-panel"){if(this._modal=e,this._render(),e.tab==="cleanup")try{const s=await this._ws({type:`${y}/get_profile_cycles`,entry_id:t,profile_name:e.name});this._modal&&this._modal.type==="profile-panel"&&this._modal.name===e.name&&(this._modal.cleanup={cycles:s.cycles||[],selected:new Set},this._render(),this._drawSpaghetti())}catch{}}else this._modal=null,this._render()}_render(){if(!this._container)return;Gt=this._pref("date_format","relative");const t=this.shadowRoot,e=t&&(t.activeElement||this.getRootNode()&&this.getRootNode().activeElement)||null;this._container.innerHTML=this._buildHtml(),this._wire(),this._drawStatusCurve(),this._drawModalCanvas(),this._drawProfileSparklines(),this._drawHistorySparklines(),this._drawPlaygroundCanvases(),["wd-status-canvas","wd-cyc-canvas","wd-compare-canvas","wd-env-canvas","wd-phase-canvas","wd-spag-canvas","wd-pgroup-canvas"].forEach(s=>this._attachHover(s)),this._syncModalFocus(e),requestAnimationFrame(()=>this._resizeLogsPage())}_resizeLogsPage(){const t=this.shadowRoot&&this.shadowRoot.getElementById("wd-log-lines-page");if(!t)return;const e=t.getBoundingClientRect();e.top<=0||(t.style.height=window.innerHeight-e.top-24+"px")}_syncModalFocus(t=null){const e=this.shadowRoot;if(!e)return;const s=e.querySelector('.wd-modal[role="dialog"]');if(s){const r=s.querySelector("h2");if(r&&!r.id&&(r.id="wd-modal-title"),this._modalFocusActive){const n=e.activeElement||this.getRootNode()&&this.getRootNode().activeElement||null;if(!n||!s.contains(n)){const a=Ft(s);try{(a[0]||s).focus()}catch{}}}else{this._modalFocusActive=!0;const n=t||null;n&&!s.contains(n)&&(this._modalReturnFocus=n);const a=Ft(s);try{(a[0]||s).focus()}catch{}}}else if(this._modalFocusActive){this._modalFocusActive=!1;const r=this._modalReturnFocus;if(this._modalReturnFocus=null,r&&r.isConnected&&typeof r.focus=="function")try{r.focus()}catch{}}}_renderPreservingFormEdits(){this._snapshotFormToPending(this.shadowRoot),this._render()}_snapshotCycleReviewForm(t){const e=this._modal;if(!t||!e||e.type!=="cycle-detail"||e.mode!=="review")return;const s=t.getElementById("wd-cyc-rev-quality"),r=t.getElementById("wd-cyc-rev-golden"),n=t.getElementById("wd-cyc-rev-notes"),a=t.getElementById("wd-cyc-rev-label");if(!s&&!r&&!n&&!a)return;e.ml||(e.ml={}),e.ml.ml_review||(e.ml.ml_review={});const o=e.ml.ml_review;s&&(o.quality=s.value||""),r&&(o.golden=!!r.checked),n&&(o.notes=n.value||""),o.tags=Array.from(t.querySelectorAll(".wd-cyc-rev-tag")).filter(i=>i.checked).map(i=>i.value),a&&e.curve&&(e.curve.profile_name=a.value||"")}_buildHtml(){const t=this._toast?`
${d(this._toast.msg)}${this._toast.actionLabel?``:""}
`:"";return`
${this._htmlHeader()}
${this._loading?`
\u23F3
${this._t("msg.loading",{},"Loading\u2026")}
`:this._htmlBody()}
${this._logOpen&&this._isAdmin()?this._htmlLogDrawer():'
'}
${this._modal?this._htmlModal():""} ${t} `}_htmlHeader(){const e=Array.from(this._busy).some(i=>!(i==="pg-sweep"||i==="pg-history"||i==="reprocess"||i.startsWith("ml-train-now")))?`${this._t("status.working",{},"Working\u2026")}`:"",s=this._constants.iconUrl,r=s?``:``,n=this._constants.version;return`
${``} ${r}

WashData

${n?`v${d(n)} · `:""}${this._t("msg.appliance_monitor",{},"Appliance monitor")}
${e} ${this._htmlTaskPills()} ${this._isAdmin()?``:""}
`}_htmlBody(){if(!this._devices.length)return`
\u{1F9FA}
${this._t("msg.no_devices",{},"No WashData devices configured yet.")}
`;const t=this._mlSugKeys().size,e=this._suggestions.length||t?" \u{1F4A1}":"",s=this._conflictKeysFromOpts().size>0?" \u26A0":"",n=this._busy.has("pg-sim")||this._busy.has("pg-sweep")?'':"",a={status:this._t("tab.status",{},"Overview"),history:this._t("tab.history",{},"Cycles"),profiles:this._t("tab.profiles",{},"Profiles"),settings:this._t("tab.settings",{},"Settings")+s+e,playground:this._t("tab.playground",{},"Playground")+n,store:this._t("tab.store",{},"Store"),advanced:this._t("tab.advanced",{},"Advanced")},o=this._visibleTabIds();o.includes(this._tab)||(this._tab="status");const i=o.map(c=>``).join(""),l=(c,_)=>o.includes(c)?`
${_}
`:"";return` ${this._htmlDeviceBar()}
${i}
${this._tabLoading?`
\u23F3
${this._t("msg.loading",{},"Loading\u2026")}
`:""} ${l("status",this._htmlStatus())} ${l("history",this._htmlHistory())} ${l("profiles",this._htmlProfiles())} ${l("settings",this._htmlSettings())} ${l("playground",this._htmlPlayground())} ${l("store",this._htmlStore())} ${l("advanced",this._htmlPanel())} `}_htmlDeviceBar(){const t=this._isAdmin()?``:"";return this._devices.length<=1?t?`
${t}
`:"":`
${this._devices.map((e,s)=>{const r=e.is_user_paused?"user_paused":e.detector_state||"unknown",n=["running","starting","paused","user_paused","ending","anti_wrinkle","rinse"].includes(r),a=!!e.recording,o=a?"var(--error-color, #f44336)":this._stateColor(r),i=a?this._t("status.recording",{},"Recording"):this._stateLabel(r),l=[],c=this._conflictCountForOpts(e.options||{},e.option_defaults||{});c&&l.push(`\u26A0 ${c}`);const _=this._sugCountsForDevice(e).total;return _&&l.push(`\u{1F4A1} ${_}`),e.feedback_count&&l.push(`\u{1F4AC} ${e.feedback_count}`),``}).join("")}${t}
`}_htmlStatus(){const t=this._devices[this._selIdx];if(!t)return`
${this._t("msg.no_device_selected",{},"No device selected.")}
`;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||["running","starting","paused","user_paused","ending","anti_wrinkle","rinse"].includes(s),i=t.cycle_progress_pct,l=t.time_remaining_s,c=t.current_program,_=!!t.manual_program,p=c||"auto_detect",g=(this._profiles||[]).map(x=>x.name);c&&!g.includes(c)&&g.unshift(c);const u=g.map(x=>``).join(""),v=c?_?this._t("badge.manual",{},"(manually selected)"):this._t("badge.auto",{},"(auto-detected)"):"",b=v?`${v}`:"",$=`
${it(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."))} ${b}
`,k=[];t.recording&&this._canEdit()&&k.push(`
\u25CF
${this._t("msg.recording_in_progress",{},"Recording in progress")}
${this._t("msg.see_recorder",{},"See recorder widget below")}
`),t.feedback_count&&this._canEdit()&&k.push(``);const S=this._conflictKeysFromOpts();if(S.size&&this._canEdit()){const x=S.size,L=x>1?"s":"";k.push(``)}const C=this._sugCountsForDevice(t);if(C.total&&this._canEdit()){const x=C.total,L=[];C.classic&&L.push(this._t("lbl.n_classic_suggestions",{n:C.classic},`${C.classic} classic`)),C.ml&&L.push(this._t("lbl.n_ml_suggestions",{n:C.ml},`${C.ml} ML`)),k.push(``)}const R=k.length?`
${k.join("")}
`:"",B=o&&i!=null?`
${i.toFixed(1)}%${l!=null?`${this._t("lbl.time_remaining",{v:ot(l)},`~${ot(l)} remaining`)}`:""}
`:"",O=((this._powerData||{}).live||[]).length>1,D=this._pref("show_expected",!0),A=this._pref("show_raw_active",!1),E=`
${this._t("lbl.power",{},"Power")} ${this._statusEnv?``:""} ${this._pref("show_raw",!1)?``:""}
`,M=this._cyclesTotal||0,H=(this._profiles||[]).length,q=this._pref("setup_card_dismissed",!1),Q=this._setupStatus,J=Q&&!O,G=J?this._htmlSetupCard(Q,q):"",X=O?`
${E}`:J?G:`

${this._t("msg.live_chart_loading",{},"Live power chart appears as readings arrive.")}

`,ct=this._pref("show_debug",!1);let U="";if(ct){const x=this._matchDebug||{},L=x.confidence!=null?`${(x.confidence*100).toFixed(1)}%`:"-",j=(x.candidates||[]).map(I=>`${d(I.profile_name)}${I.confidence_pct}%${I.mae}${I.correlation}${I.duration_ratio>=0?"+":""}${I.duration_ratio}%`).join("");U=`
Live Match Debug ${it("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.")}
${L}
${this._t("lbl.confidence",{},"Confidence")}
${x.ambiguous?this._t("status.ambiguous",{},"Ambiguous"):this._t("status.clear",{},"Clear")}
${this._t("lbl.label",{},"Match")}
${j?`${j}
ProfileConfMAECorrDuration
`:`

${this._t("msg.no_match_yet",{},"No match attempt yet - this populates during a running cycle.")}

`}
`}const at=[];this._canEdit()&&at.push(``),at.push(``);const f=`
${this._t("hdr.tools_and_data",{},"Tools & Data")}
${at.join("")}
`,m=(()=>{if(!this._canEdit())return"";const L=["running","starting","ending","anti_wrinkle","rinse"].includes(s),j=L&&!e,I=e,w=L||e;return!j&&!I&&!w?"":`
${I?``:""} ${j?``:""} ${w?``:""}
`})();return` ${R}
${d(t.title)}
${m}
${d(a)} ${!r&&t.sub_state&&t.sub_state.toLowerCase()!==s?`(${d(t.sub_state)})`:""}
${$}
${Ut(t.current_power_w)}
${this._t("lbl.power",{},"Power")}
${i!=null?i.toFixed(0)+"%":"-"}
${this._t("lbl.progress",{},"Progress")}
${ot(l)}
${this._t("lbl.remaining",{},"Remaining")}
${B} ${this._htmlPhaseTimeline(t,i,o)} ${J?"":`
${this._t("hdr.live_power",{},"Live Power")}
`} ${X}
${this._canEdit()?this._htmlRecordingWidget():""} ${U} ${f} `}_htmlSetupCard(t,e=!1){if(!t)return"";const{phase:s,message_key:r,message_params:n,cta_label_key:a,cta_action:o,secondary_label_key:i,secondary_action:l,skippable:c,dismissible:_,step_key:p}=t;if(s==="phase4")return`
${this._t("setup.hdr.healthy_chip",{},"Setup complete")}
`;if(s==="phase3"&&e)return`
${this._t("setup.hdr.card",{},"Device Setup")}
`;const g=this._t(r,n||{},""),u=this._t(a,{},"Continue"),v=i?this._t(i,{},""):null,b=c?` ${this._t("setup.cta.skip_step",{},"Skip this step")} ${this._t("setup.cta.skip_forever",{},"Don't show again")} `:"",$=_?` ${this._t("setup.cta.hide_guidance",{},"Hide guidance")} `:"";return`
${this._t("setup.hdr.card",{},"Device Setup")}

${d(g)}

${v?` ${d(v)} `:""}
${b} ${$}
`}_dispatchSetupCta(t,e){if(t){if(t==="open_recorder"){this.shadowRoot.querySelector(".wd-rec-dot")?.closest(".wd-card")?.scrollIntoView({behavior:"smooth"});return}if(t==="open_cycles"||t==="open_cycles_unlabeled"){this._tab="history",t==="open_cycles_unlabeled"&&(this._cycleFilter={...this._cycleFilter||{},status:"unlabeled"}),this._fetchTabData();return}if(t==="open_profiles"||t==="open_profiles_groups"){this._tab="profiles",this._fetchTabData();return}if(t==="open_suggestions"){this._tab="settings",this._settingsSugOnly=!0,this._fetchTabData();return}if(t==="create_profile_from_cluster"){this._tab="profiles",this._fetchTabData();return}if(t&&t.startsWith("open_cycle:")){this._tab="history",this._fetchTabData();return}}}async _reloadSetupStatus(){const t=this._devices[this._selIdx];if(t){try{this._setupStatus=await this._ws({type:`${y}/get_setup_status`,entry_id:t.entry_id})}catch{this._setupStatus=null}this._render()}}_htmlPhaseTimeline(t,e,s){const r=this._statusPhases||[];if(!s||!r.length||!t.current_program)return"";let n=this._statusEnv&&this._statusEnv.target_duration||0;if(!n){const _=(this._profiles||[]).find(p=>p.name===t.current_program);n=_&&_.avg_duration||0}if(n||(n=Math.max(1,...r.map(_=>_.end||0))),n<=0)return"";const a=e!=null?Math.min(1,Math.max(0,e/100)):null;let o="";const i=r.map((_,p)=>{const g=Math.max(0,Math.min(1,(_.start||0)/n)),u=Math.max(0,Math.min(1,(_.end||0)/n)),v=Math.max(0,(u-g)*100),b=et[p%et.length],$=a==null?!0:g<=a;a!=null&&a>=g&&a12?`${d(_.name)}`:"";return`
${k}
`}).join(""),l=a!=null?`
`:"",c=o?`
${this._t("lbl.current_phase",{},"Current phase")}: ${d(o)}
`:"";return`
${c}
`}_htmlRecordingWidget(){const t=this._recState,e=this._devices[this._selIdx],r=!!(e&&e.recording)?"recording":t?t.state:"idle",n=r==="recording"?"wd-rec-active":r==="stopped"?"wd-rec-ready":"wd-rec-idle",a=r==="recording"?this._t("status.recording",{},"Recording\u2026"):r==="stopped"?this._t("status.ready",{},"Ready to process"):this._t("status.idle",{},"Idle");let o="";r==="recording"?o=t?`${ot(t.duration_s)} \xB7 ${t.sample_count||0} samples`:"":r==="stopped"&&(o=`${t.sample_count||0} samples \xB7 ${ot(t.duration_s)}`);const i=r==="recording"?``:r==="stopped"?` `:``;return`
${this._t("hdr.manual_recording",{},"Manual Recording")}${it(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,_=this._mlById||{},p=new Set((this._feedbacks||[]).map(w=>w.cycle_id)),g=w=>_[w.id],u=w=>{const P=g(w);return!!(P&&P.ml_review&&P.ml_review.reviewed_at)},v=w=>{const P=g(w);return!!(P&&P.ml_review&&P.ml_review.golden)},b=w=>{if(p.has(w.id))return!0;if(u(w))return!1;const P=g(w),z=P&&P.ml_quality_label;return["uncertain","review"].includes(z)||["force_stopped","interrupted"].includes(w.status)},$=s.filter(b).length;let k=s;if(l){const w=l.toLowerCase();k=k.filter(P=>(P.profile_name||P.matched_profile||"").toLowerCase().includes(w))}c==="unlabelled"?k=k.filter(w=>!w.profile_name&&!w.matched_profile):c==="needs_review"?k=k.filter(b):c==="imported"?k=k.filter(w=>w.is_reference):c&&(k=k.filter(w=>(w.status||"completed")===c));const S={date:w=>w.start_time?new Date(w.start_time).getTime():0,confidence:w=>w.match_confidence,duration:w=>w.duration,energy:w=>w.energy_kwh!=null?w.energy_kwh:w.energy_wh!=null?w.energy_wh/1e3:null,cost:w=>w.cost!=null?w.cost:-1,status:w=>w.status||"completed",profile:w=>(w.profile_name||w.matched_profile||"\uFFFF").toLowerCase()};k=Qt(k,S[o]||S.date,i);const C=w=>w==="completed"?"var(--success-color, #4caf50)":w==="interrupted"?"var(--error-color, #f44336)":w==="force_stopped"?"var(--warning-color, #ff9800)":"var(--secondary-text-color)",R=w=>{if(!w.is_reference)return"";const P=w.cycle_origin==="backfill",z=P?this._t("badge.backfilled_tip",{},"Detected in imported power history. Shapes program matching only, not counted in stats."):this._t("badge.imported_tip",{},"Imported from the community store. Used for matching only, not counted in stats.");return` ${P?"\u{1F557}":"\u{1F4E5}"}`},B=w=>v(w)?' \u2B50':p.has(w.id)?' \u{1F4AC}':u(w)?' \u2713':b(w)?' \u25CF':"",W=w=>{if(w.anomaly!=="overrun")return"";const P=w.overrun_ratio?" "+this._t("badge.overrun_ratio",{x:Number(w.overrun_ratio).toFixed(1)},`(${Number(w.overrun_ratio).toFixed(1)}x expected)`):"";return` \u23F1`},O=w=>{if(w.anomaly!=="underrun")return"";const P=w.underrun_ratio?" "+this._t("badge.underrun_ratio",{pct:Math.round(w.underrun_ratio*100)},`(${Math.round(w.underrun_ratio*100)}% of expected)`):"";return` \u26A1`},D=w=>{if(!w.energy_anomaly||w.energy_anomaly==="none")return"";const P=w.energy_anomaly==="energy_spike",z=w.energy_z_score!=null?` (${w.energy_z_score>0?"+":""}${Number(w.energy_z_score).toFixed(1)}\u03C3)`:"",F=P?"badge.energy_spike":"badge.energy_low",K=P?"Higher energy than usual":"Lower energy than usual",Z=P?"\u{1F53A}":"\u{1F53B}",rt=P?"var(--error-color,#f44336)":"var(--info-color,#2196f3)";return` ${Z}`},A=w=>{const P=Array.isArray(w.artifacts)?w.artifacts.length:0;return P?` 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`:""},E=w=>{const P=Array.isArray(w.restart_gaps)?w.restart_gaps.length:0;return P?` 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||"",H=w=>w.cost!=null?`${w.cost.toFixed(2)}${M?" "+M:""}`:"-",q=k.map(w=>{const P=w.profile_name||w.matched_profile,z=w.match_confidence!=null?w.match_confidence*100:null,F=w.status||"completed",K=w.energy_kwh!=null?w.energy_kwh:w.energy_wh!=null?w.energy_wh/1e3:null,Z=n,rt=Z?``:``,ht={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")}[F]||F,nt=`${R(w)}${B(w)}${W(w)}${O(w)}${D(w)}${A(w)}${E(w)}`.trim();return` ${rt} ${P?d(P):`${this._t("lbl.unlabelled",{},"Unlabelled")}`} ${nt} ${d(ht)} ${st(w.start_time)} ${ot(w.duration)} ${K!=null?gt(K):"-"} ${H(w)} ${z!=null?z.toFixed(0)+"%":"-"} `}).join(""),Q=` ${ft(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")} ${ft(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)."))} ${ft(this._t("lbl.date",{},"Date"),"date",o==="date",i,"cycsort","",this._t("col.date_tip",{},"Date and time the cycle started."))} ${ft(this._t("lbl.duration",{},"Duration"),"duration",o==="duration",i,"cycsort","right",this._t("col.duration_tip",{},"Total cycle run time from start to end."))} ${ft(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."))} ${ft(this._t("lbl.cost",{},"Cost"),"cost",o==="cost",i,"cycsort","right",this._t("col.cost_tip",{},"Energy cost for this cycle, frozen at completion using the price in effect then (energy x price per kWh). Set a price under Settings to populate it."))} ${ft(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."))} `,J=`
`,G=k.length!==s.length?this._t("lbl.n_shown",{n:k.length},`, ${k.length} shown`):"",X=e.length?this._t("lbl.n_imported_note",{n:e.length},`, ${e.length} imported`):"",ct=this._t("lbl.cycles_title",{n:`${t.length}${X}${G}`},`Cycles (${t.length}${X}${G})`),U=r?`
`:"",at=new Set(e.map(w=>w.id)),f=[...a].some(w=>at.has(w)),m=n?`
${this._t("lbl.n_selected",{n:a.size},`${a.size} selected`)}
`:"",x=this._busy.has("cyc-load-more"),L=this._cyclesHasMore?`
`:"",j=`
${ct}
${J} ${U}${m} ${k.length===0?`
\u{1F4CB}
${s.length?this._t("msg.no_cycles_match",{},"No cycles match the current filter."):this._t("msg.no_cycles_yet",{},"No cycles recorded yet.")}
`:`
${Q}${q}
`} ${L}
`;return(this._cyclesError?`
${this._t("msg.fetch_error",{},"Failed to load data.")}
`:"")+j}_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 ${gt(t.avg_energy)}/cycle`:"",r=t.avg_energy!=null&&t.cycle_count?` \xB7 ${gt(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 k=this._trendIcon(i.duration_trend),S=i.energy_trend?this._trendIcon(i.energy_trend):"";if(i.duration_trend!=="stable"||i.energy_trend==="up"){const C=[];if(i.duration_trend!=="stable"){const B=`${i.duration_slope_pct>0?"+":""}${i.duration_slope_pct}`;C.push(i.duration_trend==="up"?this._t("msg.duration_trend_up_tip",{pct:B},`Duration up (${B}%/cycle)`):this._t("msg.duration_trend_down_tip",{pct:B},`Duration down (${B}%/cycle)`))}if(i.energy_trend&&i.energy_trend!=="stable"){const B=`${i.energy_slope_pct>0?"+":""}${i.energy_slope_pct}`;C.push(i.energy_trend==="up"?this._t("msg.energy_trend_up_tip",{pct:B},`Energy up (${B}%/cycle)`):this._t("msg.energy_trend_down_tip",{pct:B},`Energy down (${B}%/cycle)`))}const R=C.join(", ")||this._t("msg.performance_trending",{},"Performance trending");c=`${k}${S||""}`}}const _=this._constants&&this._constants.PROFILE_MIN_WARMUP_CYCLES||5,p=o&&o.cycle_count||0,u=p<_&&!t.is_imported?`${this._t("msg.warmup_badge",{done:p,needed:_},`Still learning (${p}/${_} cycles)`)}`:"",v=t.is_imported?`\u{1F4E5} ${this._t("status.imported",{},"Imported")}`:"",b=[l,c,u,v].filter(Boolean).join(" "),$=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,_=g=>c+(e.length===1?0:g/(e.length-1)*(a-2*c)),p=g=>o-c-Math.max(0,g)/l*(o-2*c);i.beginPath(),e.forEach((g,u)=>{const v=_(u),b=p(g);u===0?i.moveTo(v,b):i.lineTo(v,b)}),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(u=>{s[u.name]=u});const r=this._profileGroups||{groups:[],suggestions:[]},n=new Set;r.groups.forEach(u=>(u.members||[]).forEach(v=>n.add(v)));const a=r.groups.map(u=>{const v=(u.members||[]).map(C=>s[C]?this._profileCardHtml(s[C]):"").join(""),b=Math.round((u.cohesion!=null?u.cohesion:1)*100),$=u.cohesive?`${this._t("lbl.cohesion_good",{pct:b},"cohesion "+b+"%")}`:`${this._t("lbl.cohesion_low",{pct:b},"\u26A0 low cohesion "+b+"%")}`,k=u.cohesive?"":`

${this._t("msg.group_not_cohesive",{},"These profiles aren't similar enough to group reliably, so matching treats them individually until you remove the outlier or split the group.")}

`;return`
${e?``:`\u{1F517} ${d(u.name)}`} ${$} ${e?``:""}
${k}
${v}
`}).join(""),o=this._profiles.filter(u=>!n.has(u.name)),i=o.map(u=>this._profileCardHtml(u)).join(""),l=this._profiles.length===0&&!(this._cycles||[]).length&&!(this._refCycles||[]).length,_=(e&&this._onlineEnabled()&&l?`
\u{1F4E5} ${this._t("msg.onboard_download",{},"New device? Adopt a ready-made setup (programs, reference cycles and phases) from another WashData user with the same appliance.")}
`:"")+`
${this._t("tab.profiles",{},"Profiles")} (${this._profiles.length})

${this._t("msg.profiles_intro",{},"Click a profile for stats, phases and cleanup. Group near-identical programs (same shape/duration, different temperature or spin) so matching reliably picks between them.")}

${e?`
`:""}
${a} ${this._profiles.length===0?`
\u{1F4CA}
${this._t("msg.no_profiles_yet",{},"No profiles yet. Create one from a labelled cycle.")}
`:o.length?`${a?`
${this._t("lbl.ungrouped",{},"Ungrouped")}
`:""}
${i}
`:""}`,p=[["profiles",this._t("tab.subtab_profiles",{},"Profiles")],["phase-catalog",this._t("tab.subtab_phase_catalog",{},"Phase Catalog")]].map(([u,v])=>``).join(""),g=this._profilesError||this._profileGroupsError?`
${this._t("msg.fetch_error",{},"Failed to load data.")}
`:"";return`
${p}
${this._profSubtab==="phase-catalog"?this._htmlPhases():g+_} `}_htmlProfileGroupModal(t){const e=this._busy.has("pg-save"),s=this._profileEnvCache||{},r=p=>et[Math.max(0,this._profiles.findIndex(g=>g.name===p))%et.length],n=t.members||[],a=this._profiles.map(p=>{const g=n.includes(p.name),u=p.avg_duration?`~${Math.round(p.avg_duration/60)}m`:"",v=p.avg_energy!=null?` \xB7 ${gt(p.avg_energy)}`:"",b=g?``:"";return``}).join(""),o=n.filter(p=>s[p]&&(s[p].avg||[]).length),i=o.length?`
${o.map(p=>` ${d(p)}`).join("")}
`:"",l=o.length?`
${i}`:`

${this._t("msg.group_preview_hint",{},"Tick 2+ members to preview and compare their power curves.")}

`,c=((this._profileGroups||{}).groups||[]).find(p=>p.name===t.orig),_=c&&c.cohesion!=null?`${c.cohesive?this._t("lbl.cohesion_good",{pct:Math.round(c.cohesion*100)},"cohesion "+Math.round(c.cohesion*100)+"%"):this._t("lbl.cohesion_low",{pct:Math.round(c.cohesion*100)},"\u26A0 low cohesion "+Math.round(c.cohesion*100)+"%")}`:"";return`

${t.orig?this._t("modal.edit_group",{},"Edit profile group"):this._t("modal.new_group",{},"New profile group")}

${_}
${l}
${this._t("lbl.members",{},"Members")}${n.length?` (${n.length})`:""}
${a||`${this._t("msg.no_profiles_yet_short",{},"No profiles yet.")}`}

${this._t("msg.group_modal_help",{},"Group programs with the same shape that differ in temperature/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.")}

${t.orig?``:""}
`}_drawGroupCanvas(){const t=this._modal;if(!t||t.type!=="profile-group")return;const e=this._profileEnvCache||{},s=a=>et[Math.max(0,this._profiles.findIndex(o=>o.name===a))%et.length];let r=0;const n=(t.members||[]).filter(a=>e[a]&&(e[a].avg||[]).length).map(a=>{const o=e[a],i=o.avg[o.avg.length-1];return r=Math.max(r,o.target_duration||(i?i[0]:0)),{points:o.avg,stroke:s(a),width:2,alpha:.9,name:a}});n.length&&this._drawCurves("wd-pgroup-canvas",{series:n,xMax:r})}_settingsLevel(){return this._pref("settings_level","basic")==="advanced"?"advanced":"basic"}_settingFieldVisible(t){return this._settingsLevel()==="advanced"||!!t.basic}_secHasBasicFields(t){return(t.fields||(t.groups||[]).flatMap(s=>s.fields||[])).some(s=>s.basic)}_htmlSettings(){const t=Object.assign({},this._opts,this._pendingSettings);if(!Object.keys(t).length)return`
\u2699\uFE0F
${this._t("msg.loading_settings",{},"Loading settings\u2026")}
`;const e=this._suggestionsError?`
${this._t("msg.fetch_error",{},"Failed to load data.")}
`:"",r=this._settingsLevel()==="basic",n=new Set((this._suggestions||[]).map(U=>U.key)),a=this._mlSugKeys(t),o=new Set([...n,...a]),i=U=>(U.fields||(U.groups||[]).flatMap(f=>f.fields||[])).some(f=>o.has(f.key)),l=this._conflictKeysFromOpts(),c=U=>(U.fields||(U.groups||[]).flatMap(f=>f.fields||[])).some(f=>l.has(f.key)),_=this._opts&&this._opts.device_type||"",p=mt.filter(U=>!(U.id==="ml_training"||_&&U.notDeviceTypes&&U.notDeviceTypes.includes(_)||_&&U.onlyDeviceTypes&&!U.onlyDeviceTypes.includes(_)||r&&!this._secHasBasicFields(U))),g=(p.find(U=>U.id===this._settingsSec)||p[0]||{}).id,u=p.map(U=>{const at=i(U),f=c(U);return``}).join(""),v=``,b=r?`

${this._t("msg.settings_basic_note",{},"Showing essential settings. Switch to Advanced for the full list.")}

`:"",$=this._busy.has("save-settings"),k=l.size,S=k!==1?"s":"",C=k?`
\u26A0 ${this._t("conflict.settings_banner",{n:k},`Setting conflicts: ${k}. Check the highlighted sections and fix them before saving.`)}
`:"",R=this._suggestions.length,B=[...a].filter(U=>!n.has(U)).length,W=R+B,O=this._settingsSugOnly&&!this._settingsSearch,D=R?``:"",A=R?``:"",E=W?O?`
\u{1F4A1} ${this._t("msg.showing_suggestions",{count:W},`Showing ${W} setting${W>1?"s":""} with suggestions.`)} ${this._t("msg.show_all_settings",{},"Show all settings")}. ${D}
`:`
\u{1F4A1} ${this._t("msg.tuning_suggestions_available",{count:W},`${W} tuning suggestion${W>1?"s":""} available from observed cycles. They appear beside the relevant fields.`)} ${D} ${A}
`:"",M=this._busy.has("sug-analyze"),H=``,q=this._settingsSearch||"",Q=q.trim().toLowerCase(),J=``,G=Q?this._htmlSettingsSearch(t,Q):O?this._htmlSettingsSugOnly(t):this._htmlSettingsSection(t),X=(this._lockedSuggestions||[]).length,ct=X?`
\u{1F515} ${this._t("msg.n_suggestions_muted",{count:X},`${X} suggestion${X>1?"s":""} muted; the auto-tuner will not propose these.`)}
`:"";return` ${e}
${this._t("tab.settings",{},"Settings")}${this._mlSettingsLoading?` ${this._t("msg.ml_loading",{},"loading ML\u2026")}`:""}
${H}
${C}${E}${ct}${b}
${J}
${u}
${v}
${G}

${this._t("msg.saving_triggers_reload",{},"Saving triggers an integration reload. HA entities may briefly show as unavailable.")}

${this._htmlSettingsHistory()} `}_htmlSettingsHistory(){const t=this._settingsChangelog||[];if(!t.length)return"";const e=this._settingsHistoryOpen,s=this._canEdit(),r=t.slice(0,100).map(n=>{const a=s?``:"";return` ${d(this._t("setting."+n.key+".label",{},n.key))} ${d(Tt(n.old))} \u2192 ${d(Tt(n.new))} ${st(n.timestamp)} ${a} `}).join("");return`
${this._t("hdr.settings_history",{},"Settings history")} (${t.length})
${e?"\u25B2":"\u25BC"}
${e?`
${r}
${this._t("lbl.setting",{},"Setting")} ${this._t("lbl.change",{},"Change")} ${this._t("lbl.date",{},"Date")}
`:""}
`}_renderField(t,e){if(t.onlyDeviceType&&(e.device_type||"washing_machine")!==t.onlyDeviceType)return"";if(t.type==="storebrand"||t.type==="storemodel")return this._renderStorePicker(t,e);let s=e[t.key];if(s===void 0){const l=this._optDefaults;s=l&&l[t.key]!=null?l[t.key]:t.def}const r={};if(t.type==="devicetype")r.opts=this._deviceTypeOpts(s||e.device_type);else if(t.type==="device")r.opts=this._deviceOpts();else if(t.type==="select")r.opts=t.opts||[];else if(t.type==="entity"){const l=this._hass&&this._hass.states?this._hass.states:{},c=t.domain==="binary_sensor"?["binary_sensor","sensor"]:t.domain?[t.domain]:null,_=Object.keys(l).filter(p=>!c||c.some(g=>p.startsWith(g+"."))).sort().slice(0,500);this._entityListCache||(this._entityListCache={}),this._entityListCache[t.key]=_}else if(t.type==="entitylist"){const l=this._hass&&this._hass.states?this._hass.states:{},c=Object.keys(l).filter(_=>!t.domain||_.startsWith(t.domain+".")).sort();if(t.domain==="notify"&&this._hass&&this._hass.services&&this._hass.services.notify){const _=Object.keys(this._hass.services.notify).map(p=>`notify.${p}`);r.entities=[...new Set([...c,..._])].sort().slice(0,500)}else r.entities=c.slice(0,500);this._entityListCache||(this._entityListCache={}),this._entityListCache[t.key]=r.entities}else if(Array.isArray(t.suggestions)&&t.suggestions.length){const l=`wd-dl-${t.key}`;r.datalistId=l,r.datalist=`${t.suggestions.map(c=>``}const n=this._suggestions.find(l=>l.key===t.key);if(n){const l=Object.assign({},n.reason_params||{});n.exclusions&&n.exclusions.total&&(l.excl=this._exclNote(n.exclusions)),r.suggestion={suggested:n.suggested,current:n.current,reason:n.reason,reason_key:n.reason_key,reason_params:l}}const a=(this._mlSettings||{})[t.key];a&&a.ml_value!=null&&!(this._lockedSuggestions||[]).includes(t.key)&&(r.mlSuggestion={value:a.ml_value,reason:a.ml_reason,reason_key:a.ml_reason_key,reason_params:a.ml_reason_params}),r.useBtnLabel=this._t("btn.use",{},"Use"),r.t=this._t.bind(this);const o=(this._settingsChangeByKey||{})[t.key];o&&(r.changed=this._t("msg.setting_changed",{old:Tt(o.old),new:Tt(o.new),date:st(o.timestamp)},`Changed from ${Tt(o.old)} to ${Tt(o.new)} on ${st(o.timestamp)}`));const i=Object.assign({},t,{label:this._t("setting."+t.key+".label",{},t.label||""),doc:t.doc!=null?this._t("setting."+t.key+".doc",{},t.doc):t.doc});return _e(i,s,r)}_renderStorePicker(t,e){const s=t.type==="storebrand",r=t.key,n=String(e[r]==null?"":e[r]),a=this._t("setting."+r+".label",{},t.label||""),o=this._t("setting."+r+".doc",{},t.doc||""),i=d(this._t("placeholder."+r,{},s?"e.g. Bosch":"e.g. WAT28660"));return this._onlineEnabled()?s?this._renderBrandPicker(r,n,a,o,i):this._renderModelPicker(r,e,n,a,o,i):`
${d(this._t("msg.store_picker_offline",{},"Enable online features in the settings gear to pick from the community catalog."))}
`}_statusTag(t){if(t&&t.status==="pending"){const e=d(this._t("badge.awaiting_tip",{},"Awaiting community approval")),s=typeof t.confirmCount=="number"?this._t("badge.awaiting_n",{n:t.confirmCount},`Awaiting approval \xB7 ${t.confirmCount} confirmed`):this._t("badge.awaiting",{},"Awaiting approval");return`${s}`}return t&&t.status==="approved"?`${this._t("badge.approved",{},"Approved")}`:""}_renderBrandPicker(t,e,s,r,n){this._ensureCatalogEntry();const a=Array.isArray(this._catalog.brands)?this._catalog.brands:[];this._entityListCache=this._entityListCache||{},this._entityListCache[t]=a.map(l=>l.brand).filter(Boolean);const o=this._statusTag(this._catalogEntryFor(e,"brand")),i=this._catalog.brands===null?` ${this._t("msg.loading",{},"Loading\u2026")}`:"";return`
`}_renderModelPicker(t,e,s,r,n,a){const o=String(e.store_brand||"");if(!o)return`
${d(this._t("msg.pick_brand_first",{},"Pick an appliance brand first."))}
`;this._ensureCatalogEntry(),this._catalog.forBrand!==o&&(this._catalog.forBrand=o,this._catalog.devices=void 0);const i=Array.isArray(this._catalog.devices)?this._catalog.devices:[];this._entityListCache=this._entityListCache||{},this._entityListCache[t]=i.map(u=>u.model).filter(Boolean);const l=this._catalogEntryFor(s,"device"),c=this._statusTag(l),_=this._catalog.devices===null?` ${this._t("msg.loading",{},"Loading\u2026")}`:"";let p="";if(l){const u=[],v=pe(l.manualUrl);v&&u.push(`${this._t("link.manual",{},"Manual \u2197")}`),(this._constants&&this._constants.storePrefs||{}).show_contributor!==!1&&u.push(this._t("store.contributed_by",{name:d(l.createdByName||this._t("lbl.anonymous",{},"Anonymous"))},`by ${d(l.createdByName||"Anonymous")}`));const b=!!(this._storeStatus&&this._storeStatus.connected);let $="";if(b){const k=[1,2,3,4,5].map(S=>``).join("");$=` ${k}`}else $=`${this._t("msg.connect_to_confirm",{},"Connect in the settings gear to confirm or rate.")}`;p=`
${u.join(" \xB7 ")}
${$}
`}const g=this._onlineEnabled()&&this._storeDeviceDeclared()?`
`:"";return`
${p}${g}
`}_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&&this.shadowRoot.getElementById(t);if(r&&this.shadowRoot.activeElement===r){s&&r.dispatchEvent(new Event("input",{bubbles:!0}));return}this._isActiveEntry(e)&&this._render()}_ensureCatalogList(t,e){if(this._onlineEnabled()){if(t==="store_brand"){this._ensureBrandCandidates(e);return}if(t==="store_model"){const s=String((this._opts||{}).store_brand||"").trim();if(!s)return;(this._catalog.forBrand!==s||this._catalog.devices===void 0)&&(this._catalog.forBrand=s,this._catalog.devices=null,this._loadCatalogDevices(s))}}}_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._refreshComboAfterLoad("wd-store-brand",s.entry_id,!e)}}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.")}

${r}
${e}
${this._t("btn.from_template",{},"From template \u25BE")}
`}async _loadDeviceAutomations(t){this._deviceAutomations=[];const e=this._hass;if(!(!e||!e.callWS))try{let s=null;const 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}),_=c&&c.options||{},p=Array.isArray(_.notify_actions)?_.notify_actions:[];l=p.length>0,this._isActiveEntry(t.entry_id)&&(this._opts={...this._opts,notify_actions:p})}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=mt.find(l=>l.id===this._settingsSec&&r(l))||mt.find(l=>r(l))||mt[0],a=n.intro||this._t("section."+n.id+".intro",{},"")?`

${d(this._t("section."+n.id+".intro",{},n.intro||""))}

`:"",o="";if(n.id==="notifications"){const l=`

${this._t("msg.notify_services_hint",{entity:"notify.<name>",vars:""+d($t)+""},"Use {entity} service IDs (comma-separated for multiple). Template variables: {vars}.")}

`,c=n.groups.map(p=>{const g=(p.fields||[]).filter(u=>this._settingFieldVisible(u)).map(u=>this._renderField(u,t)).filter(Boolean).join("");return g?`
${d(this._t("setting_group."+te(p.sub)+".label",{},p.sub))}
${g}
`:""}).join("");return`${s?"":this._htmlAutomations()}${l}${c}`}if(n.groups){const l=n.groups.map(c=>{const _=c.sub?`
${d(this._t("setting_group."+te(c.sub)+".label",{},c.sub))}
`:"",p=(c.fields||[]).filter(g=>this._settingFieldVisible(g)).map(g=>this._renderField(g,t)).filter(Boolean).join("");return p?`${_}
${p}
`:""}).join("");return`${a}${o}${l}`}const i=(n.fields||[]).filter(l=>this._settingFieldVisible(l)).map(l=>this._renderField(l,t)).filter(Boolean).join("");return`${a}${o}
${i}
`}_htmlSettingsSearch(t,e){const s=this._opts&&this._opts.device_type||"",r=mt.filter(i=>!(i.id==="ml_training"||s&&i.notDeviceTypes&&i.notDeviceTypes.includes(s)||s&&i.onlyDeviceTypes&&!i.onlyDeviceTypes.includes(s))),n=i=>`${i.label||""} ${i.key||""} ${i.doc||""} ${i.hint||""}`.toLowerCase().includes(e);let a="",o=0;for(const i of r){const c=(i.fields||(i.groups||[]).flatMap(p=>p.fields||[])).filter(n);if(!c.length)continue;const _=c.map(p=>this._renderField(p,t)).filter(Boolean).join("");_&&(o+=c.length,a+=`
${d(this._t("section."+i.id+".label",{},i.label))}
${_}
`)}return o?a:`

${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&&!Ot(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=mt.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+=`
${d(this._t("section."+a.id+".label",{},a.label))}
${l}
`)}return n||`

${this._t("msg.no_suggestions",{},"No active suggestions.")}

`}_htmlMlTab(){const t=this._opts;if(!Object.keys(t).length)return`
\u{1F916}
${this._t("msg.loading",{},"Loading\u2026")}
`;const e=this._mlTrainingStatus,s=this._devices[this._selIdx],r=s&&s.entry_id,n=mt.find(i=>i.id==="ml_training"),a=n?(n.fields||[]).map(i=>this._renderField(i,t)).filter(Boolean).join(""):"",o=this._busy.has("save-settings");return`
${this._t("hdr.ml_smart_learning",{},"Smart Learning")}

${this._t("msg.ml_intro",{},"WashData ships with smart models that work out of the box.")}

${this._htmlMlStatusSection(e,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.")}

${a}

${this._t("msg.saving_triggers_reload",{},"Saving triggers an integration reload.")}

${this._htmlMlLearnedSection(e)} ${this._htmlMatchingTuningCard()} `}_htmlMlStatusSection(t,e){const s=e&&this._busy.has("ml-train-now:"+e)||t&&t.running,r=this._canEdit()?``:"";if(!t)return`
${this._t("hdr.status",{},"Status")}

${this._t("msg.loading",{},"Loading\u2026")}

`;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,_=l?"var(--success-color,#4caf50)":"var(--warning-color,#ff9800)",p=Math.max(0,i-o),g=l?this._t("msg.enough_data",{current:o,min:i},`Enough data to learn from (${o}/${i} cycles).`):this._t("msg.collecting_data",{need:p,current:o,min:i},`Collecting data. Cycles still needed before fine-tuning can start: ${p} (${o}/${i}).`),u=`
`,v=t.last_trained?st(t.last_trained):"never",b=s?` ${this._t("status.fine_tuning",{},"fine-tuning now\u2026")}`:t.enabled?this._t("lbl.auto_fine_tune_on",{hour:String(t.hour).padStart(2,"0")},`auto fine-tune on (around ${String(t.hour).padStart(2,"0")}:00)`):this._t("lbl.auto_fine_tune_off",{},"auto fine-tune off");return`
${this._t("hdr.status",{},"Status")}
${r}
${a}

${g}

${u}

${this._t("lbl.last_checked",{},"Last checked:")} ${d(v)} \xB7 ${b}

`}_htmlMlLearnedSection(t){if(!t)return"";const e=t.on_device_models||{},s=Object.keys(e),r=this._busy.has("ml-revert-models");let n;if(!s.length)n=`

${this._t("msg.no_fine_tuned",{},"Nothing fine-tuned yet \u2014 WashData is using its built-in models.")}

`;else{const a=s.map(i=>{const l=e[i]||{},c=l.trained_at?st(l.trained_at):"unknown";return`
${d(l.label_key?this._t(l.label_key,{},l.label||i):l.label||i)}${this._mlTrendBadge(l.trend)}
${d(l.blurb_key?this._t(l.blurb_key,{},l.blurb||""):l.blurb||"")} \xB7 ${this._t("ml.fine_tuned_at",{when:d(c)},"fine-tuned "+d(c))}
${this._mlQualityChip(l)}
`}).join(""),o=this._canEdit()?``:"";n=`
${a}
${o}`}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=g=>g==null||isNaN(g)?"-":Number(g).toFixed(2),l=[["corr_weight","Shape (correlation)"],["duration_weight","Duration agreement"],["energy_weight","Energy agreement"],["dtw_ensemble_w","DTW derivative blend (DDTW)"]].map(([g,u])=>{const v=s[g],b=a?n[g]:s[g],$=a&&v!=null&&b!=null&&Math.abs(v-b)>1e-9;return` ${u} ${i(v)} ${i(b)} `}).join(""),c=a?`${this._t("badge.using_tuned",{},"Using tuned weights")}`:`${this._t("badge.using_defaults",{},"Using shipped defaults")}`;let _="";if(a){const g=r.trained_at?st(r.trained_at):"unknown",u=r.baseline_test_top1,v=r.tuned_test_top1,b=u!=null&&v!=null?` \xB7 held-out top-1 ${(u*100).toFixed(0)}% \u2192 ${(v*100).toFixed(0)}%`:"";_=`

Tuned ${d(g)} from ${r.cycle_count||0} cycles${b}.

`}const p=a?``:"";return`
${this._t("hdr.ml_matching_tuning",{},"Program-matching fine-tuning")}
${p}

${this._t("msg.matching_tuning_intro",{},"When learning, WashData also adjusts how much program matching weighs shape versus duration and energy.")}

${c}
${l}
${this._t("lbl.emphasis",{},"Emphasis")}${this._t("lbl.default",{},"Default")}${this._t("lbl.in_use",{},"In use")}
${_}
`}_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"],["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=kt[t]||{};if(a.def!==void 0)return a.def;const o=this._constants&&this._constants.pgMatchDefaults||{};return o[t]!==void 0?o[t]:qt[t]!==void 0?qt[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):!!kt[t]}_htmlPgControlPanel(){const t=this._pgChangedKeys(),e=this._canEdit(),s=!!this._pgEffective,r=t.filter(b=>this._pgIsPublishable(b)),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(b=>``).join(""),o=!!this._pgPresetSel,i=this._pgPresetLimit>0&&(this._pgPresets||[]).length>=this._pgPresetLimit&&!(this._pgPresets||[]).some(b=>b.name===(this._pgPresetName||"").trim()),l=`
${e?``:""}
`,c=e?`
${this._t("msg.pg_preset_limit",{n:this._pgPresetLimit},"Preset limit reached ("+this._pgPresetLimit+")")}
`:"",_=e&&r.length?``:"",p=Object.keys(this._pgSuggClassic||{}).length,g=Object.keys(this._pgSuggMl||{}).length,u=p>0?``:"",v=this._pgMlSuggEnabled&&g>0?``:"";return`
${this._t("hdr.pg_settings_source",{},"Settings source")} ${n}

${this._t("msg.pg_ctrl_intro",{},"Values start from this device's live integration settings. Edits stay in the Playground until you publish them.")}

${u}${v} ${_}
${l} ${c}
`}_pgApplyPresetValues(t){this._pgThreshStart=null,this._pgThreshStop=null,this._pgParamOverrides={};const e=this._pgEffective||{};for(const[s,r]of Object.entries(t||{})){if(r==null)continue;const n=e[s];n!=null&&this._pgSameVal(r,n)||this._pgSetStaged(s,r)}}async _pgSavePreset(){const t=this._devices[this._selIdx],e=(this._pgPresetName||"").trim();!t||!this._canEdit()||!e||(this._pgPresets||[]).some(s=>s.name===e)&&!confirm(this._t("msg.pg_preset_overwrite",{name:e},`Overwrite the preset "${e}"?`))||(await this._busyRun("pg-preset-save",async()=>{try{const s=await this._ws({type:`${y}/save_playground_preset`,entry_id:t.entry_id,name:e,values:this._pgCurrentValues()});if(!this._isActiveEntry(t.entry_id))return;this._pgPresets=Array.isArray(s.presets)?s.presets:this._pgPresets,this._pgPresetSel=e,this._pgPresetName="",this._showToast(this._t("toast.pg_preset_saved",{name:e},`Preset "${e}" saved`))}catch(s){this._showToast(this._t("msg.toast_save_failed",{error:s.message||s},"Save failed: "+(s.message||s)),"error")}}),this._render())}async _pgDeletePreset(){const t=this._devices[this._selIdx],e=this._pgPresetSel;!t||!this._canEdit()||!e||confirm(this._t("msg.pg_preset_delete_confirm",{name:e},`Delete the preset "${e}"?`))&&(await this._busyRun("pg-preset-delete",async()=>{try{const s=await this._ws({type:`${y}/delete_playground_preset`,entry_id:t.entry_id,name:e});if(!this._isActiveEntry(t.entry_id))return;this._pgPresets=Array.isArray(s.presets)?s.presets:(this._pgPresets||[]).filter(r=>r.name!==e),this._pgPresetSel=""}catch(s){this._showToast(this._t("msg.toast_error",{error:s.message||s},"Error: "+(s.message||s)),"error")}}),this._render())}async _pgLoadLive(){const t=this._devices[this._selIdx];if(!t)return;const e=this._pgChangedKeys();e.length&&!confirm(this._t("msg.pg_load_live_confirm",{n:e.length},`Discard ${e.length} Playground edit(s) and reload the integration's current settings?`))||(await this._busyRun("pg-load-live",async()=>{this._pgThreshStart=null,this._pgThreshStop=null,this._pgParamOverrides={},await this._pgFetchSettings(t.entry_id)}),this._render(),requestAnimationFrame(()=>this._pgDrawCanvas()))}_pgLoadSuggested(t){const e=t==="ml"?this._pgSuggMl||{}:this._pgSuggClassic||{},s=this._pgEffective||{};let r=0;for(const[n,a]of Object.entries(e)){if(a==null)continue;const o=s[n];o!=null&&this._pgSameVal(a,o)||(this._pgSetStaged(n,a),r++)}if(r===0)this._showToast(this._t("msg.pg_sugg_none",{},"All suggestions already match the current settings"));else{const n=t==="ml"?"toast.pg_sugg_ml_loaded":"toast.pg_sugg_loaded",a=t==="ml"?`Staged ${r} ML-calibrated value(s) - run the Playground to compare`:`Staged ${r} suggested value(s) - run the Playground to compare`;this._showToast(this._t(n,{n:r},a))}this._render(),requestAnimationFrame(()=>this._pgDrawCanvas())}async _pgPublishOne(t){const e=this._devices[this._selIdx];if(!e||!this._canEdit()||!t||!this._pgIsPublishable(t))return;const s=this._pgStagedVal(t);if(s==null)return;const r=this._t("setting."+t+".label",{},t);confirm(this._t("msg.pg_publish_one_confirm",{label:r,value:s},`Save ${r} = ${s} to this device's settings?`))&&(await this._busyRun("pg-publish-"+t,async()=>{try{if(await this._ws({type:`${y}/set_options`,entry_id:e.entry_id,options:{[t]:s}}),!this._isActiveEntry(e.entry_id))return;this._opts={...this._opts,[t]:s},this._pgEffective&&(this._pgEffective={...this._pgEffective,[t]:s}),this._pgClearStaged(t),this._showToast(this._t("toast.settings_saved",{},"Settings saved; integration reloading"))}catch(n){this._showToast(this._t("msg.toast_save_failed",{error:n.message||n},"Save failed: "+(n.message||n)),"error")}}),this._render())}_htmlPlayground(){if(!this._devices[this._selIdx])return`
${this._t("msg.no_device_selected",{},"No device selected.")}
`;const e=this._cycles||[],s=this._profiles||[],r=e.map(u=>{const v=u.profile_name||u.matched_profile||this._t("lbl.unlabelled",{},"Unlabelled"),b=u.duration?` \xB7 ${Math.round(u.duration/60)} min`:"",$=u.start_time?` \xB7 ${st(u.start_time)}`:"";return``}).join(""),n=``+s.map(u=>``).join(""),a=this._pgLoading,o=`
${a?``:""}
`,i=a?`
${this._t("msg.pg_simulating",{},"Simulating cycle\u2026")}
`:"",l=!this._pgPowerPts&&!a?`
${this._t("msg.pg_canvas_empty2",{},"Pick a cycle above and press Run to simulate it. Then hover to read values, scroll to zoom, and drag to pan.")}
`:"",c=`${i}
${l}
`,_=this._htmlPgStrip(),p=this._pgNeedsRestart?`

\u26A0 ${this._t("msg.pg_restart_note",{},"Restart Home Assistant to enable simulation tools.")}

`:"",g=`${o}${c}${_}
${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.")}

${p} ${g} ${this._htmlPgDrawer()}
`}_htmlPgDrawer(){const t=this._pgAnalysisTab||"history",s=`
${[["history",this._t("lbl.pg_mode_history",{},"Test on history")],["sweep",this._t("lbl.pg_mode_optimize",{},"Optimize")]].map(([n,a])=>``).join("")}
`,r=t==="sweep"?this._htmlPgSweepMode():this._htmlPgHistoryMode();return`
${this._t("hdr.pg_across_cycles",{},"Across your cycles")} ${s}
${r}
`}_htmlPgParamRows(){const t=this._pgOverrideFields(),e=new Set(["start_threshold_w","stop_threshold_w"]),s={detection:"#2a78d6",timing:"#1baf7a",advanced:"#eda100",matching:"#a05cd6"},r={detection:this._t("lbl.pg_group_detection",{},"Detection triggers"),timing:this._t("lbl.pg_group_timing",{},"Timing rules"),advanced:this._t("lbl.pg_group_advanced",{},"Edge cases"),matching:this._t("lbl.pg_group_matching",{},"Program matching")};let n="";const a=t.map(([p,g,u,v,b,$])=>{const k=this._t("setting."+p+".label",{},g),S=this._pgFieldVal(p,{});let C;p==="start_threshold_w"?C=this._pgThreshStart??S:p==="stop_threshold_w"?C=this._pgThreshStop??S:C=this._pgParamOverrides[p]??S;const R=$==="bool",B=e.has(p),W=u||"",O=s[b]||"#2a78d6",D=r[b]||"";let A="";b&&b!==n&&(A=`
${d(D)}
`,n=b);const E=this._pgStagedVal(p),M=E!=null&&!(S!==""&&S!==null&&S!==void 0&&this._pgSameVal(E,S)),H=this._canEdit()&&M&&this._pgIsPublishable(p)?``:'';return`${A}
${d(k)}${B?` \u2195`:""}
${v?`
${d(this._t("pg_desc."+p,{},v))}
`:""}
${R?``:``} ${W?`${d(W)}`:""} ${H}
`}).join(""),i=`
${d(this._t("lbl.pg_stress_group",{},"Idle termination test"))}
`,l=`
${d(this._t("lbl.pg_stress_toggle",{},"Test idle termination"))}
${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."))}
`,c=this._pgStressTail?`
${d(this._t("lbl.pg_stress_idle_w",{},"Idle level (W)"))}
${d(this._t("lbl.pg_stress_idle_w_desc",{},"Override the auto-detected standby floor (leave blank for auto)."))}
W
`:"",_=`${i}${l}${c}`;return`
${this._t("hdr.pg_detection_params",{},"Detection settings")}
${this._htmlPgControlPanel()} ${a}${_}
${this._pgDetailBusy?'':""}
`}_htmlPgAlerts(){const t=this._pgDetail;if(!t)return"";const e=t.outcome||{},s={error:"var(--error-color,#f44336)",warn:"var(--warning-color,#ff9800)",info:"var(--info-color,#2196f3)"},r=Array.isArray(t.alerts)?t.alerts:[],n=r.length?r.map(c=>`
${d(this._pgAlertLabel(c.code))}
${d(c.detail_key?this._t(c.detail_key,c.detail_params||{},c.detail||""):c.detail||"")}
`).join(""):`
\u2713 ${this._t("msg.pg_no_alerts",{},"No issues detected in this run.")}
`,a=e.termination_reason?String(e.termination_reason):"\u2014",o=e.final_duration_s?Math.round(e.final_duration_s/60)+" min":"\u2014",i=e.projected_energy_wh!=null?e.projected_energy_wh>=1e3?(e.projected_energy_wh/1e3).toFixed(2)+" kWh":Math.round(e.projected_energy_wh)+" Wh":"\u2014",l=(c,_)=>`
${d(_)}
${d(c)}
`;return`
${this._t("hdr.pg_outcome",{},"Simulation outcome")}
${l(this._t("lbl.pg_ended",{},"Ended"),a)} ${l(this._t("lbl.duration",{},"Duration"),o)} ${l(this._t("lbl.pg_proj_energy",{},"Proj. energy"),i)}
${n}
`}_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.")}

`;if(!e||!Array.isArray(e.rows))return`${a}${r}${this._htmlPgRecentRuns("pg_history")}${n}${t?"":`
${this._t("msg.pg_history_empty",{},"Press Run to replay your cycles.")}
`}`;let o="";if(e.diff){const u=(v,b,$)=>`${v} ${d($)}`;o=`
${u((e.diff.newly_correct||[]).length,"var(--success-color,#4caf50)",this._t("lbl.pg_newly_correct",{},"newly correct"))} ${u((e.diff.regressed||[]).length,"var(--error-color,#f44336)",this._t("lbl.pg_regressed",{},"regressed"))} ${u((e.diff.end_timing_changed||[]).length,"var(--warning-color,#ff9800)",this._t("lbl.pg_end_timing_changed",{},"end-timing changed"))}
`}const i=e.summary||{},l=`
${this._t("msg.pg_history_summary",{detected:i.detected,correct:i.match_correct,total:i.cycles},`${i.detected}/${i.cycles} detected \xB7 ${i.match_correct} matched correctly`)}
`,c=u=>(this._cycles||[]).find(v=>v.id===u),_={};(e.baseline_rows||[]).forEach(u=>{_[u.cycle_id]=u});const p=e.rows.map(u=>{const v=c(u.cycle_id),b=v&&v.start_time?st(v.start_time):(u.cycle_id||"").slice(0,8),$=u.match_correct===!0?"\u2713":u.match_correct===!1?"\u2717":"\u2014",k=u.match_correct===!0?"var(--success-color,#4caf50)":u.match_correct===!1?"var(--error-color,#f44336)":"var(--secondary-text-color)",S=u.matched_profile||this._t("lbl.unlabelled",{},"Unlabelled"),C=u.duration_s?Math.round(u.duration_s/60)+"m":"\u2014",R=u.overrun_ratio!=null?` (${Math.round(u.overrun_ratio*100)}%)`:"",B=u.alerts&&u.alerts.length?` \u26A0`:"",W=_[u.cycle_id];let O="";return W&&(W.match_correct!==!0&&u.match_correct===!0?O=`\u25B2 ${this._t("lbl.pg_fixed",{},"fixed")}`:W.match_correct===!0&&u.match_correct!==!0?O=`\u25BC ${this._t("lbl.pg_broke",{},"broke")}`:String(W.termination_reason)!==String(u.termination_reason)&&(O=`${d(String(W.termination_reason||"\u2014"))}\u2192${d(String(u.termination_reason||"\u2014"))}`)),` ${d(b)} ${$} ${d(S)} ${d(String(u.termination_reason||"\u2014"))} ${C}${R}${B} ${e.diff?`${O}`:""} `}).join(""),g=` ${e.diff?``:""} ${p}
${this._t("lbl.cycle",{},"Cycle")} ${this._t("lbl.match",{},"Match")} ${this._t("lbl.pg_ended",{},"Ended")} ${this._t("lbl.duration",{},"Duration")}${this._t("lbl.pg_vs_current",{},"vs current")}
`;return`${a}${r}${this._htmlPgRecentRuns("pg_history")}${o}${l}${g}`}_htmlPgBatchBar(){const t=this._pgBatchProgress;return t?`
${t.done}/${t.total}
`:""}_pgUpdateBatchBar(t,e){const s=this.shadowRoot;if(!s)return;const r=s.getElementById("wd-pg-batch-fill"),n=s.getElementById("wd-pg-batch-count"),a=e?Math.round(100*t/e):0;r&&(r.style.width=a+"%"),n&&(n.textContent=String(t))}async _pgRunHistory(){const t=this._devices[this._selIdx];if(!t)return;const e=(this._cycles||[]).slice(0,Math.max(1,this._pgSimCycles||20)).map(r=>r.id);if(!e.length){this._showToast(this._t("msg.no_cycles_selected",{},"No cycles available."),"error");return}const s={...this._pgParamOverrides};this._pgThreshStart!=null&&(s.start_threshold_w=this._pgThreshStart),this._pgThreshStop!=null&&(s.stop_threshold_w=this._pgThreshStop),this._pgBatchProgress={done:0,total:e.length},this._busy.add("pg-history"),this._render();try{const r=await this._ws({type:`${y}/start_playground_history`,entry_id:t.entry_id,cycle_ids:e,settings_override:s});if(this._pgHistoryTaskId=r&&r.task_id,this._pgNeedsRestart=!1,!this._pgHistoryTaskId)throw new Error("no task id");this._addProvisionalTask(this._pgHistoryTaskId,"pg_history",t.entry_id,e.length),this._tasksSubscribed||this._pgPollTask(this._pgHistoryTaskId)}catch(r){this._busy.delete("pg-history"),this._pgBatchProgress=null,this._pgIsUnknownCmd(r)?this._pgNeedsRestart=!0:this._showToast(this._t("msg.toast_error",{error:r.message||r},"Error: "+(r.message||r)),"error"),this._render()}}_pgSweepObjectives(){return[["match_accuracy",this._t("lbl.pg_obj_match",{},"Match accuracy"),!1],["end_timing_accuracy",this._t("lbl.pg_obj_endtiming",{},"End-timing accuracy"),!1],["false_end_rate",this._t("lbl.pg_obj_falseend",{},"False-end rate"),!0],["median_overrun",this._t("lbl.pg_obj_overrun",{},"Duration off-target"),!0],["ambiguity_rate",this._t("lbl.pg_obj_ambiguity",{},"Ambiguity rate"),!0]]}_htmlPgSweepMode(){const t=this._busy.has("pg-sweep"),s=this._pgOverrideFields().filter(([,,,,,i])=>i!=="bool").map(([i,l])=>``).join(""),r=this._pgSweepObjectives().map(([i,l])=>``).join(""),n=`

${this._t("msg.pg_sweep_intro2",{},"Find the setting that best meets an objective across your recent cycles. Nothing changes until you apply it.")}

`,a=`
`,o=t?this._htmlPgBatchBar():"";return`${n}${a}${this._htmlPgRecentRuns("pg_sweep")}${o}${this._htmlPgSweepResult()}`}_htmlPgSweepResult(){const t=this._pgSweepNew;if(!t||!Array.isArray(t.points)||!t.points.length)return"";const e=this._pgSweepObjectives().find(p=>p[0]===t.objective),s=e?e[2]:!1,r=t.points.filter(p=>p.metric!=null).map(p=>p.metric);if(!r.length)return`
${this._t("msg.pg_sweep_no_metric",{},"Not enough data to score this objective.")}
`;const n=Math.min(...r),a=Math.max(...r),o=s?Math.min(...r):Math.max(...r),i=(t.points.find(p=>p.metric===o)||{}).value,l=p=>t.objective==="median_overrun"?Math.round(p*100)+"% off":Math.round(p*100)+"%",c=t.points.map(p=>{const g=a>n&&p.metric!=null?(p.metric-n)/(a-n):p.metric!=null?1:0,u=p.metric===o,v=t.current_value!=null&&Math.abs(p.value-t.current_value)<1e-6,b=u?"var(--success-color,#4caf50)":"var(--primary-color)";return`
${d(String(p.value))}${v?" \u25C0":""}
${p.metric!=null?l(p.metric):"\u2014"}
`}).join(""),_=this._canEdit()&&i!=null?``:"";return`
${this._t("lbl.pg_best_value",{},"Best value found")}: ${d(String(i))} \xB7 ${l(o)} \xB7 \u25C0 ${this._t("lbl.pg_current_value",{},"Current value:")}
${c}
${_}
`}async _pgRunSweep2(){const t=this._devices[this._selIdx];if(!t)return;const e=parseFloat(this._pgSweepFrom),s=parseFloat(this._pgSweepTo);if(isNaN(e)||isNaN(s)||e===s){this._showToast(this._t("msg.toast_name_required",{},"Set a valid From/To range."),"error");return}const r=Math.max(2,Math.min(12,this._pgSweepSteps||5)),n=Array.from({length:r},(l,c)=>+(e+(s-e)*c/(r-1)).toFixed(3)),a=this._pgSweepParam||"off_delay",o=this._pgSweepObjective||"match_accuracy",i={type:`${y}/start_playground_sweep`,entry_id:t.entry_id,param:a,values:n,objective:o};this._pgBatchProgress={done:0,total:n.length},this._busy.add("pg-sweep"),this._render();try{const l=await this._ws(i);if(this._pgSweepTaskId=l&&l.task_id,this._pgNeedsRestart=!1,!this._pgSweepTaskId)throw new Error("no task id");this._addProvisionalTask(this._pgSweepTaskId,"pg_sweep",t.entry_id,n.length),this._tasksSubscribed||this._pgPollTask(this._pgSweepTaskId)}catch(l){this._busy.delete("pg-sweep"),this._pgBatchProgress=null,this._pgIsUnknownCmd(l)?this._pgNeedsRestart=!0:this._showToast(this._t("msg.toast_error",{error:l.message||l},"Error: "+(l.message||l)),"error"),this._render()}}async _pgApplyToSettings(){const t=this._devices[this._selIdx];if(!t||!this._canEdit())return;const e={};for(const n of this._pgChangedKeys())this._pgIsPublishable(n)&&(e[n]=this._pgStagedVal(n));const s=Object.keys(e);if(!s.length)return;const r=s.map(n=>this._t("setting."+n+".label",{},n)).join(", ");confirm(this._t("msg.pg_apply_settings_confirm",{n:s.length,list:r},`Save these ${s.length} setting(s) to this device? ${r}`))&&(await this._busyRun("pg-apply-settings",async()=>{try{if(await this._ws({type:`${y}/set_options`,entry_id:t.entry_id,options:e}),!this._isActiveEntry(t.entry_id))return;this._opts={...this._opts,...e},this._pgEffective&&(this._pgEffective={...this._pgEffective,...e});for(const n of s)this._pgClearStaged(n);this._showToast(this._t("toast.settings_saved",{},"Settings saved; integration reloading"))}catch(n){this._showToast(this._t("msg.toast_save_failed",{error:n.message||n},"Save failed: "+(n.message||n)),"error")}}),this._render())}async _pgApplySweepValue(t){const e=this._devices[this._selIdx];if(!e||!this._canEdit()||t==null)return;const s=this._pgSweepParam,r=this._t("setting."+s+".label",{},s);confirm(this._t("msg.pg_apply_confirm",{label:r,value:t},"Apply best value: "+r+" = "+t+"?"))&&await this._busyRun("pg-sweep-apply",async()=>{try{await this._ws({type:`${y}/set_options`,entry_id:e.entry_id,options:{[s]:+t}}),this._opts={...this._opts,[s]:+t},s==="start_threshold_w"?this._pgThreshStart=+t:s==="stop_threshold_w"?this._pgThreshStop=+t:this._pgParamOverrides[s]=+t,this._showToast(this._t("toast.settings_saved",{},"Settings saved; integration reloading")),this._render()}catch(n){this._showToast(this._t("msg.toast_save_failed",{error:n.message||n},"Save failed: "+(n.message||n)),"error")}})}_htmlPgStrip(){return`
${this._t("lbl.pg_idle",{},"Idle")} ${this._t("lbl.power",{},"Power")} \u2014 \u2014% ${this._t("lbl.pg_time_left_model",{},"Time left (model)")} \u2014 ${this._t("lbl.energy",{},"Energy")} \u2014 ${this._t("lbl.match",{},"Match")} \u2014 ${this._t("lbl.phase",{},"Phase")} \u2014
`}_htmlPgAnalysis(){const t=this._pgDtwData,e=(i,l,c,_,p)=>{const g=c&&l!=null?Math.max(0,Math.min(1,l/c)):l!=null?Math.max(0,Math.min(1,l)):0;return`
${d(i)}
${p!=null?d(String(p)):"\u2014"}
`};let s="";if(t&&(t.stage2||t.stage4)){const i=t.stage2||{},l=t.stage4||{},c=t.dtw||{},_=l.final_score??c.blended_score??i.score,p=this._pgDetail&&this._pgDetail.outcome&&this._pgDetail.outcome.confidence,g=p??_;let u="\u2014",v="var(--secondary-text-color)";g!=null&&(g>=.7?(u="\u2705 "+this._t("lbl.pg_strong_match",{},"Strong match"),v="var(--success-color, #4caf50)"):g>=.4?(u="\u26A0 "+this._t("lbl.pg_weak_match",{},"Weak match"),v="var(--warning-color, #ff9800)"):(u="\u274C "+this._t("lbl.pg_poor_match",{},"Poor match"),v="var(--error-color, #f44336)"));const b=t.profile_name||this._pgProfileName||"\u2014";s+=`
${u}
`,b!=="\u2014"&&(s+=`
${d(b)} \xB7 ${d(this._t("lbl.score",{},"score"))} ${_!=null?_.toFixed(3):"\u2014"}
`),s+=e(this._t("lbl.correlation",{},"Correlation"),i.correlation,1,"#42a5f5",i.correlation!=null?i.correlation.toFixed(2):null),c.blended_score!=null&&(s+=e(this._t("lbl.pg_dtw",{},"DTW"),c.blended_score,1,"#ab47bc",c.blended_score.toFixed(2))),l.duration_agreement!=null&&(s+=e(this._t("lbl.duration",{},"Duration"),l.duration_agreement,1,"#66bb6a",l.duration_agreement.toFixed(2))),l.energy_agreement!=null&&(s+=e(this._t("lbl.energy",{},"Energy"),l.energy_agreement,1,"#ffa726",l.energy_agreement.toFixed(2))),s+='
'}else t||(s+=`

${this._t("msg.pg_analysis_empty2",{},"Press Run to see match analysis.")}

`);const r=this._pgDetail&&this._pgDetail.outcome,n=r&&r.matched_profile||t&&t.profile_name||this._pgProfileName||(this._cycles||[]).find(i=>i.id===this._pgCycleId)?.profile_name||"",a=r&&r.confidence;if(n&&a!=null){const i=Math.round(Math.max(0,Math.min(1,a))*100);s+=`
${d(this._t("lbl.pg_match_confidence",{},"Match confidence"))}
`,s+=`
${d(n)}
${i}%
`}const o=t&&t.stage4&&t.stage4.final_score;if(o!=null){const i=Math.round(Math.max(0,Math.min(1,o))*100);s+=`
${d(this._t("lbl.pg_envelope_fit",{},"Envelope fit"))}
`,s+=`
${i}%
`}return`
${s||`

${this._t("msg.pg_analysis_hint2",{},"Pick a cycle and press Run to load match analysis.")}

`}
`}async _pgLoad(){const t=this._devices[this._selIdx];if(!t||this._pgLoading)return;const e=this._pgCycleId||this._cycles?.[0]?.id||"";if(!e)return;this._pgCycleId=e,this._pgLoading=!0,this._pgView=null,this._pgHoverT=null,this._pgPowerPts=null,this._pgDtwData=null,this._pgEnvData=null,this._pgDetail=null;const s=++this._pgLoadSeq;this._render();try{const r=await this._ws({type:`${y}/get_cycle_power_data`,entry_id:t.entry_id,cycle_id:e}),n=r.samples||[],a=[];for(const l of n){if(!Array.isArray(l)||l.length<2)continue;const c=+l[0],_=+l[1];!isNaN(c)&&!isNaN(_)&&a.push({t:c,w:_})}if(this._pgPowerPts=a.length?a:null,typeof r.full_duration_s=="number"&&r.full_duration_s>0){const l=(this._cycles||[]).find(c=>c.id===e);l&&(l._pg_duration=r.full_duration_s)}const o=this._pgProfileName||(this._cycles||[]).find(l=>l.id===e)?.profile_name||"";if(a.length)try{const l={type:`${y}/get_dtw_debug`,entry_id:t.entry_id,cycle_id:e};o&&(l.profile_name=o),this._pgDtwData=await this._ws(l),this._pgNeedsRestart=!1}catch(l){this._pgIsUnknownCmd(l)&&(this._pgNeedsRestart=!0),this._pgDtwData=null}const i=this._pgDtwData?.profile_name||o;if(i)try{const l=await this._ws({type:`${y}/get_profile_envelope`,entry_id:t.entry_id,profile_name:i});this._pgEnvData=l.envelope||null}catch{this._pgEnvData=null}if(s!==this._pgLoadSeq)return;await this._pgLoadDetail(t.entry_id,e)}catch(r){this._showToast(this._t("msg.toast_error",{error:r.message||r},"Error: "+(r.message||r)),"error")}s===this._pgLoadSeq&&(this._pgLoading=!1,this._render(),requestAnimationFrame(()=>{this._pgDrawCanvas(),this._pgUpdateStripAt(null)}),this._pgNeedsRestart&&this._tab==="playground"?(this._pgRestartRetries||0)<5&&!this._pgRestartRetryTimer&&(this._pgRestartRetries=(this._pgRestartRetries||0)+1,this._pgRestartRetryTimer=setTimeout(()=>{this._pgRestartRetryTimer=null,this._pgNeedsRestart&&this._tab==="playground"&&this._pgLoad()},3e3)):this._pgRestartRetries=0)}_pgCancelRun(){this._pgLoadSeq++,this._pgLoading=!1,this._render()}_pgSelectCycle(t){t&&(this._pgCycleId=t,this._pgProfileName="",this._pgView=null,this._pgHoverT=null,this._pgLoading=!1,this._pgLoad(),requestAnimationFrame(()=>{const e=this.shadowRoot&&this.shadowRoot.getElementById("wd-pg-canvas");e&&typeof e.scrollIntoView=="function"&&e.scrollIntoView({behavior:"smooth",block:"center"})}))}async _pgLoadDetail(t,e){const s={...this._pgParamOverrides};this._pgThreshStart!=null&&(s.start_threshold_w=this._pgThreshStart),this._pgThreshStop!=null&&(s.stop_threshold_w=this._pgThreshStop);try{const r=await this._ws({type:`${y}/start_playground_cycle_detail`,entry_id:t,cycle_id:e,settings_override:s,stress_tail:this._pgStressTail,stress_idle_w:this._pgStressIdleW!=null?parseFloat(this._pgStressIdleW):null}),n=r&&r.task_id;if(!n)throw new Error("no task id");this._addProvisionalTask(n,"pg_detail",t,0);const a=await new Promise(o=>{this._taskCallbacks[n]=async l=>{if(l.state==="done"||l.state==="cancelled")try{const c=await this._ws({type:`${y}/get_task_result`,task_id:l.id});o(c&&c.result)}catch{o(null)}else o(null)};const i=this._tasks[n];i&&i.state!=="running"?this._settleTaskCallback(i):this._tasksSubscribed||this._pollTaskGeneric(n)});if(!this._isActiveEntry(t))return;this._pgDetail=a&&!a.error?a:null,this._pgNeedsRestart=!1}catch(r){this._pgIsUnknownCmd(r)&&(this._pgNeedsRestart=!0),this._pgDetail=null}}_pgRerunDetail(){const t=this._devices[this._selIdx];!t||!this._pgCycleId||(clearTimeout(this._pgDetailDebounceTimer),this._pgDetailDebounceTimer=setTimeout(async()=>{this._pgDetailBusy=!0,this._render(),await this._pgLoadDetail(t.entry_id,this._pgCycleId),this._pgDetailBusy=!1,this._render(),requestAnimationFrame(()=>this._pgDrawCanvas())},220))}_pgMapState(t){return t==="running"||t==="paused"?"running":t==="ending"?"ending":t==="starting"?"detecting":t==="anti_wrinkle"?"anti_wrinkle":"idle"}_pgSeriesAt(t){const e=this._pgDetail&&this._pgDetail.series;if(!e||!e.length)return null;let s=e[0];for(const r of e)if(r.t<=t)s=r;else break;return s}_pgStateSegsFromSeries(t){const e=this._pgDetail&&this._pgDetail.series;if(!e||!e.length)return[];const s=[];let r=null;for(const n of e){const a=this._pgMapState(n.state);!r||r.state!==a?(r={start:n.t,end:n.t,state:a},s.push(r)):r.end=n.t}return s.length&&(s[s.length-1].end=t),s}_pgDrawCanvas(){if(this._tab!=="playground")return;const t=this.shadowRoot,e=t&&t.getElementById("wd-pg-canvas");if(!e)return;const s=this._pgPowerPts,r=e.getBoundingClientRect(),n=window.devicePixelRatio||1,a=Math.max(1,Math.round(r.width*n)),o=Math.max(1,Math.round((r.height||280)*n));(e.width!==a||e.height!==o)&&(e.width=a,e.height=o);const i=e.getContext("2d"),l=getComputedStyle(this),c=(l.getPropertyValue("--primary-color")||"#03a9f4").trim(),_=(l.getPropertyValue("--divider-color")||"rgba(127,127,127,.2)").trim(),p=(l.getPropertyValue("--secondary-text-color")||"#888").trim(),g=(l.getPropertyValue("--secondary-background-color")||"#1a1a1a").trim();i.clearRect(0,0,a,o);const u=34*n,v=14*n,b=Bt*n,$=44*n,k=8*n,S=b+8*n,C=u+v+4*n,R=o-S-C;if(!s||!s.length){this._pgLoading&&(i.fillStyle=p,i.font=`${12*n}px sans-serif`,i.textAlign="center",i.textBaseline="middle",i.fillText(this._t("msg.loading",{},"Loading\u2026"),a/2,o/2));return}const B=this._pgDetail&&this._pgDetail.outcome&&this._pgDetail.outcome.stress,W=B&&B.enabled?B.synthetic_from_s:null,O=W!=null&&this._pgDetail&&this._pgDetail.series?this._pgDetail.series.filter(T=>T.t>=W):[];let D=(this._cycles||[]).find(T=>T.id===this._pgCycleId)?._pg_duration||s[s.length-1].t||1;B&&B.terminated&&B.terminated_after_s!=null&&W!=null?D=Math.max(D,W+B.terminated_after_s):O.length&&(D=Math.max(D,O[O.length-1].t));const A=Math.max(...s.map(T=>T.w),...O.map(T=>T.power||0),1),E=this._pgThreshStart??this._pgFieldVal("start_threshold_w",{})??50,M=this._pgThreshStop??this._pgFieldVal("stop_threshold_w",{})??5;let H=0,q=D;this._pgView&&this._pgView.max-this._pgView.min>1&&(H=Math.max(0,this._pgView.min),q=Math.min(D,this._pgView.max),q-H<=1&&(H=0,q=D));const Q=Math.max(1e-6,q-H),J=a-$-k,G=T=>$+(T-H)/Q*J,X=T=>S+(1-Math.max(0,T)/A)*R;this._pgMap={vMin:H,vMax:q,totalDur:D,padLpx:$/n,plotWpx:J/n},i.strokeStyle="rgba(127,127,127,0.12)",i.lineWidth=n,i.setLineDash([]),[.25,.5,.75,1].map(T=>Math.round(T*A/100)*100||Math.round(T*A)).forEach(T=>{const N=X(T);NS+R||(i.beginPath(),i.moveTo($,N),i.lineTo(a-k,N),i.stroke(),i.fillStyle=p,i.font=`${9*n}px sans-serif`,i.textAlign="right",i.textBaseline="middle",i.fillText(T+"W",$-4*n,N))}),i.save(),i.beginPath(),i.rect($,S,J,R),i.clip();const U=this._pgEnvData,at=U&&Array.isArray(U.avg)?U.avg.filter(T=>Array.isArray(T)&&T.length>=2):[];if(at.length){const T=Math.max(...at.map(V=>V[0]))||1,N=V=>G(V/T*D),tt=Array.isArray(U.min)?U.min.filter(V=>Array.isArray(V)&&V.length>=2):[],Y=Array.isArray(U.max)?U.max.filter(V=>Array.isArray(V)&&V.length>=2):[];if(tt.length&&Y.length){i.beginPath(),Y.forEach((V,lt)=>lt?i.lineTo(N(V[0]),X(V[1])):i.moveTo(N(V[0]),X(V[1])));for(let V=tt.length-1;V>=0;V--)i.lineTo(N(tt[V][0]),X(tt[V][1]));i.closePath(),i.fillStyle="#eda10012",i.fill()}else i.beginPath(),i.strokeStyle="#eda100",i.lineWidth=2*n,i.setLineDash([5*n,4*n]),at.forEach((V,lt)=>lt?i.lineTo(N(V[0]),X(V[1])):i.moveTo(N(V[0]),X(V[1]))),i.stroke(),i.setLineDash([])}const f=this._pgDtwData,m=f&&Array.isArray(f.profile_trace)?f.profile_trace.filter(T=>Array.isArray(T)&&T.length>=2):[],x=f&&Array.isArray(f.cycle_trace)?f.cycle_trace.filter(T=>Array.isArray(T)&&T.length>=2):[],L=f&&Array.isArray(f.warp_path)?f.warp_path:[];if(x.length&&m.length&&L.length){const T=Math.max(...m.map(Y=>Y[0]))||1,N=Math.max(...x.map(Y=>Y[0]))||1,tt=Math.max(1,Math.floor(L.length/25));i.save(),i.globalAlpha=.13,i.strokeStyle="#fff",i.lineWidth=n;for(let Y=0;YN[0]))||1;i.beginPath(),i.strokeStyle="#eda100",i.lineWidth=2*n,i.setLineDash([6*n,4*n]),m.forEach((N,tt)=>{const Y=G(N[0]/T*D),V=X(N[1]);tt?i.lineTo(Y,V):i.moveTo(Y,V)}),i.stroke(),i.setLineDash([])}if(i.beginPath(),i.moveTo(G(0),X(0)),s.forEach(T=>i.lineTo(G(T.t),X(T.w))),i.lineTo(G(s[s.length-1].t),X(0)),i.closePath(),i.fillStyle=St(c,.1),i.fill(),i.beginPath(),i.strokeStyle=c,i.lineWidth=2*n,s.forEach((T,N)=>N?i.lineTo(G(T.t),X(T.w)):i.moveTo(G(T.t),X(T.w))),i.stroke(),O.length){const T=s[s.length-1];i.beginPath(),i.moveTo(G(T.t),X(T.w)),O.forEach(N=>i.lineTo(G(N.t),X(N.power))),i.lineTo(G(O[O.length-1].t),X(0)),i.lineTo(G(T.t),X(0)),i.closePath(),i.fillStyle=St(c,.06),i.fill(),i.beginPath(),i.strokeStyle=c,i.lineWidth=1.5*n,i.setLineDash([4*n,4*n]),i.moveTo(G(T.t),X(T.w)),O.forEach(N=>i.lineTo(G(N.t),X(N.power))),i.stroke(),i.setLineDash([])}if(W!=null&&W{const Y=X(T);YS+R+2||(i.save(),i.strokeStyle=N,i.lineWidth=2*n,i.setLineDash([8*n,4*n]),i.beginPath(),i.moveTo($,Y),i.lineTo(a-k,Y),i.stroke(),i.setLineDash([]),i.fillStyle=N,i.beginPath(),i.arc($+(a-$-k)*.06,Y,5*n,0,Math.PI*2),i.fill(),i.fillStyle=N,i.font=`bold ${9*n}px sans-serif`,i.textAlign="left",i.textBaseline="bottom",i.fillText(tt+" "+Math.round(T)+"W",$+14*n,Y-2*n),i.restore())};j(+E,"#2a78d6",this._t("lbl.start",{},"Start")),j(+M,"#e34948",this._t("btn.stop",{},"Stop"));const I={idle:g,detecting:"#42a5f566",running:"#66bb6a66",ending:"#ef535066",anti_wrinkle:"#ab47bc66"},w={idle:this._t("lbl.pg_idle",{},"Idle"),detecting:this._t("lbl.pg_detecting",{},"Detecting"),running:this._t("lbl.pg_ev_running",{},"Running"),ending:this._t("lbl.pg_ev_ending",{},"Ending"),anti_wrinkle:this._t("lbl.pg_anti_wrinkle",{},"Anti-wrinkle")},P=o-u-v;i.fillStyle=g,i.fillRect($,P,a-$-k,u),i.save(),i.beginPath(),i.rect($,P,J,u),i.clip(),this._pgStateSegsFromSeries(D).forEach(T=>{const N=G(T.start),tt=G(T.end);i.fillStyle=I[T.state]||_,i.fillRect(N,P,Math.max(1,tt-N),u),tt-N>50*n&&(i.fillStyle=p,i.font=`${8*n}px sans-serif`,i.textAlign="center",i.textBaseline="middle",i.fillText((w[T.state]||T.state).toUpperCase(),(N+tt)/2,P+u/2))}),i.restore();const F=(this._cycles||[]).find(T=>T.id===this._pgCycleId),K=this._pgDtwData?.profile_name||this._pgProfileName||F?.profile_name,Z=K?(this._profiles||[]).find(T=>T.name===K):null,rt=o-v;Z&&Array.isArray(Z.phases)&&Z.phases.length&&(i.save(),i.beginPath(),i.rect($,rt,J,v),i.clip(),Z.phases.forEach((T,N)=>{const tt=G((T.start||0)*D),Y=G((T.end||1)*D),V=N*47%360;i.fillStyle=`hsla(${V},60%,55%,0.55)`,i.fillRect(tt,rt,Math.max(1,Y-tt),v),Y-tt>40*n&&(i.fillStyle="#fff",i.font=`${7*n}px sans-serif`,i.textAlign="center",i.textBaseline="middle",i.fillText(T.name,(tt+Y)/2,rt+v/2))}),i.restore()),this._pgEventHits=[];const ht=this._pgDetail&&Array.isArray(this._pgDetail.events)?this._pgDetail.events.filter(T=>T.type!=="state"&&T.t>=H&&T.t<=q).slice().sort((T,N)=>T.t-N.t):[],nt=11*n,Ct=S-b/2-2*n,se=nt*2+3*n;let jt=-1/0;const xt=this._pgHoverEvent;if(ht.forEach(T=>{const N=this._pgEventMeta(T.type),tt=G(T.t);let Y=Math.max(tt,jt+se);Y=Math.max($+nt,Math.min(a-k-nt,Y)),jt=Y;const V=xt&&xt.t===T.t&&xt.type===T.type;i.save(),i.strokeStyle=N.color,i.globalAlpha=V?.9:.5,i.lineWidth=(V?2:1)*n,i.beginPath(),i.moveTo(Y,Ct+nt),i.lineTo(tt,S),i.stroke(),V&&(i.globalAlpha=.3,i.setLineDash([2*n,3*n]),i.beginPath(),i.moveTo(tt,S),i.lineTo(tt,o-v),i.stroke(),i.setLineDash([])),i.restore(),i.save(),i.beginPath(),i.arc(Y,Ct,nt,0,Math.PI*2),i.fillStyle=V?N.color:g||"#1a1a1a",i.fill(),i.lineWidth=(V?2:1.5)*n,i.strokeStyle=N.color,i.stroke(),i.fillStyle=V?"#fff":N.color,i.font=`${14*n}px sans-serif`,i.textAlign="center",i.textBaseline="middle",i.fillText(N.glyph,Y,Ct+n),i.restore(),this._pgEventHits.push({cx:Y/n,cy:Ct/n,r:nt/n,type:T.type,label:N.label,detail:T.detail,t:T.t})}),xt){const T=this._pgEventHits.find(N=>N.t===xt.t&&N.type===xt.type);if(T){const N=this._pgEventMeta(T.type),tt=ut=>`${Math.floor(ut/60)}:${String(Math.round(ut%60)).padStart(2,"0")}`,Y=this._pgEventDescription(T.type),V=[{t:T.label,bold:!0,col:N.color},...Y?[{t:Y}]:[],...T.detail?[{t:T.detail,dim:!0}]:[],{t:`${this._t("lbl.from_start",{},"From start")} ${tt(T.t)}`,dim:!0}];i.save(),i.font=`${10*n}px sans-serif`;const lt=Math.min(220*n,a-2*$),pt=[];V.forEach(ut=>{const At=String(ut.t).split(" ");let vt="";At.forEach(Lt=>{const Wt=vt?vt+" "+Lt:Lt;i.measureText(Wt).width>lt&&vt?(pt.push({...ut,t:vt}),vt=Lt):vt=Wt}),pt.push({...ut,t:vt})});const bt=14*n,_t=Math.min(lt+14*n,a-8*n),yt=pt.length*bt+10*n;let wt=T.cx*n-_t/2;wt=Math.max(4*n,Math.min(a-_t-4*n,wt));const It=Ct+nt+6*n;i.fillStyle=g,i.globalAlpha=.97,i.fillRect(wt,It,_t,yt),i.globalAlpha=1,i.strokeStyle=N.color,i.lineWidth=n,i.strokeRect(wt,It,_t,yt),i.textAlign="left",i.textBaseline="top",pt.forEach((ut,At)=>{i.font=`${ut.bold?"600 ":""}${10*n}px sans-serif`,i.fillStyle=ut.col||(ut.dim?p:(l.getPropertyValue("--primary-text-color")||"#ddd").trim()),i.fillText(ut.t,wt+7*n,It+5*n+At*bt)}),i.restore()}}if(this._pgHoverT!=null&&this._pgHoverT>=H&&this._pgHoverT<=q){const T=G(this._pgHoverT);i.save(),i.strokeStyle="#e34948",i.lineWidth=1.5*n,i.setLineDash([4*n,3*n]),i.beginPath(),i.moveTo(T,S),i.lineTo(T,o-v),i.stroke(),i.setLineDash([]);const N=this._pgInterpPower(s,this._pgHoverT),tt=X(N);i.fillStyle="#e34948",i.beginPath(),i.arc(T,tt,3.5*n,0,Math.PI*2),i.fill();const Y=dt=>`${Math.floor(dt/60)}:${String(Math.round(dt%60)).padStart(2,"0")}`,V=dt=>dt>=1e3?(dt/1e3).toFixed(2)+" kW":Math.round(dt)+" W",lt=[`${this._t("lbl.from_start",{},"From start")} ${Y(this._pgHoverT)}`,`${this._t("lbl.to_end",{},"To end")} ${Y(Math.max(0,D-this._pgHoverT))}`,`${this._t("lbl.power",{},"Power")} ${V(N)}`];i.font=`${9.5*n}px sans-serif`;const pt=Math.max(...lt.map(dt=>i.measureText(dt).width))+12*n,bt=lt.length*13*n+8*n;let _t=T+8*n;_t+pt>a-k&&(_t=T-pt-8*n);const yt=S+4*n;i.fillStyle=g,i.globalAlpha=.95,i.fillRect(_t,yt,pt,bt),i.globalAlpha=1,i.strokeStyle=_,i.lineWidth=n,i.strokeRect(_t,yt,pt,bt),i.fillStyle=p,i.textAlign="left",i.textBaseline="top",lt.forEach((dt,wt)=>i.fillText(dt,_t+6*n,yt+5*n+wt*13*n)),i.restore()}i.fillStyle=p,i.font=`${9*n}px sans-serif`,i.textAlign="center",i.textBaseline="top";const ie=T=>`${Math.floor(T/60)}:${String(Math.round(T%60)).padStart(2,"0")}`;for(let T=0;T<=4;T++){const N=H+Q*T/4,tt=G(N);i.textAlign=T===0?"left":T===4?"right":"center",i.fillText(ie(N),Math.max($,Math.min(a-k,tt)),P+u+1*n)}QD.id===this._pgCycleId)?._pg_duration||e[e.length-1].t||1,n=t??r,a=this._pgInterpPower(e,n),o=this._pgSeriesAt(n),i=o?this._pgMapState(o.state):"idle",l={idle:[this._t("lbl.pg_idle",{},"Idle"),"var(--secondary-background-color)"],detecting:[this._t("lbl.pg_detecting",{},"Detecting"),"#42a5f5"],running:[this._t("lbl.pg_ev_running",{},"Running"),"#66bb6a"],ending:[this._t("lbl.pg_ev_ending",{},"Ending"),"#ef5350"],anti_wrinkle:[this._t("lbl.pg_anti_wrinkle",{},"Anti-wrinkle"),"#ab47bc"]},[c,_]=l[i]||l.idle,p=o&&o.progress!=null?Math.round(o.progress):null,g=o&&o.remaining_s!=null?o.remaining_s:null,u=o&&o.energy_wh!=null?o.energy_wh:this._pgTrapEnergy(e,n),v=o&&o.confidence!=null?Math.round(o.confidence*100)+"%":"\u2014",b=o&&o.phase?o.phase:"\u2014",$=D=>Math.floor(D/60)+":"+String(Math.round(D%60)).padStart(2,"0"),k=D=>D>=1e3?(D/1e3).toFixed(2)+" kWh":D.toFixed(0)+" Wh",S=D=>D>=1e3?(D/1e3).toFixed(1)+" kW":Math.round(D)+" W",C=this.shadowRoot,R=D=>C&&C.getElementById(D),B=(D,A)=>{const E=R(D);E&&(E.textContent=A)},W=(D,A,E)=>{const M=R(D);M&&(M.style[A]=E)},O=R("wd-pg-state-badge");O&&(O.textContent=c,O.style.background=_,O.style.color=_.includes("var")?"":"#fff"),B("wd-pg-power",S(a)),B("wd-pg-pct",p!=null?p+"%":"\u2014%"),W("wd-pg-pbar","width",(p??0)+"%"),B("wd-pg-rem",g!=null?$(g):"\u2014"),B("wd-pg-energy",k(u)),B("wd-pg-conf",v),B("wd-pg-phase",b)}_pgIsUnknownCmd(t){const e=t&&(t.code||t.error&&t.error.code)||"",s=(t&&(t.message||t.error)||"").toString().toLowerCase();return e==="unknown_command"||s.includes("unknown command")||s.includes("unknown_command")}_pgInterpPower(t,e){if(!t.length)return 0;if(e<=t[0].t)return t[0].w;for(let s=1;s=e))break}return s}_drawPlaygroundCanvases(){this._tab==="playground"&&this._pgDrawCanvas()}_htmlPhases(){const t=this._devices[this._selIdx],e=t&&t.options.device_type||"washing_machine",s=this._canEdit(),r=this._phases.map(o=>{const i=o.is_default,l=o.translation_key?this._t(o.translation_key,{},o.description||""):o.description||"",c=s?` ${i?"":``} `:"";return` ${d(o.name)} ${i?`${this._t("badge.built_in_tag",{},"built-in")}`:""} ${d(l.length>60?l.slice(0,57)+"\u2026":l)} ${c} `}).join(""),n=s?`${this._t("lbl.actions",{},"Actions")}`:"",a=s?`
`:"";return`
${this._t("hdr.phase_catalog",{},"Phase Catalog")}

${this._t("msg.phase_catalog_intro",{},"Named segments of a cycle (Pre-wash, Heating, Spin\u2026). Assign them to a profile from its control panel.")}

${a} ${this._phases.length===0?`

${this._t("msg.no_phases",{},"No phases defined.")}

`:`${n}${r}
${this._t("lbl.phase_name",{},"Name")}${this._t("lbl.description",{},"Description")}
`}
`}_htmlDiagnostics(){const t=this._diag;let e;return t&&t._error?e=`

${this._t("msg.diagnostics_load_failed",{error:d(t._error)},"Could not load diagnostics: "+d(t._error))}

`:t?e=`
${t.total_cycles??"-"}
${this._t("lbl.cycles_count",{},"Cycles")}
${t.total_profiles??"-"}
${this._t("tab.profiles",{},"Profiles")}
${t.debug_traces_count??"-"}
${this._t("lbl.debug_traces",{},"Debug Traces")}
${t.file_size_kb!=null?t.file_size_kb.toFixed(1):"-"}
${this._t("lbl.file_kb",{},"File (kB)")}
`:e=`

${this._t("msg.loading",{},"Loading\u2026")}

`,`
${this._t("hdr.storage_stats",{},"Storage Stats")}
${e}
${this._canFull()?`
${this._t("hdr.maintenance",{},"Maintenance Actions")}
${this._t("hdr.process_history",{},"Process History")}

${this._t("msg.process_history_hint",{},"Re-run matching on all stored cycles, refresh tuning suggestions, retrain the ML models (if enabled), and recompute cycle health. Run this after a batch of reviews.")}

${this._t("hdr.clear_debug",{},"Clear Debug Traces")}

${this._t("msg.clear_debug_hint",{},"Remove stored debug data to free space.")}

${this._t("hdr.wipe_history",{},"Wipe History")}

${this._t("msg.wipe_history_warning",{},"Permanently delete all cycles and profiles. Cannot be undone.")}

${this._t("hdr.export_import",{},"Export / Import")}

${this._t("msg.export_description",{},"Choose exactly which profiles, cycles, settings and more to export to JSON, or analyze a file and import only the parts you want.")}

${this._t("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.")}

`}`}_maintLabel(t){return{descale:this._t("maint.descale",{},"Descale"),filter_clean:this._t("maint.filter_clean",{},"Clean filter"),drum_clean:this._t("maint.drum_clean",{},"Clean drum"),bearing_service:this._t("maint.bearing_service",{},"Bearing service"),other:this._t("maint.other",{},"Other")}[t]||t}_htmlMaintenance(){const t=this._canEdit(),e=this._maintenance;if(e&&e._error)return`

${this._t("msg.maintenance_load_error",{error:e._error},"Could not load maintenance data: "+e._error)}

`;if(!e)return`

${this._t("msg.loading",{},"Loading\u2026")}

`;const s=e.event_types&&e.event_types.length?e.event_types:["descale","filter_clean","drum_clean","bearing_service","other"],r=e.due||[],n=e.log||[],a=e.reminders||{},o=r.length?(()=>{const g=r.map(u=>this._maintLabel(u)).join(", ");return`
${this._t("msg.maintenance_due",{items:d(g)},"Maintenance due: "+g)}
`})():"",i=new Date().toISOString().slice(0,10),l=s.map(g=>``).join(""),c=t?`
${this._t("hdr.add_maintenance",{},"Add Maintenance Event")}
`:"",_=n.length?n.map(g=>{const u=t?``:"",v=g.notes?`
${d(g.notes)}
`:"";return`
${d(this._maintLabel(g.event_type))}
${st(g.date)}
${v}
${u}
`}).join(""):`

${this._t("msg.no_maintenance",{},"No maintenance recorded yet.")}

`,p=t?`
${this._t("hdr.maintenance_reminders",{},"Service Reminders")}

${this._t("msg.reminders_intro",{},"Show a reminder in the panel this many cycles after the last service. Leave blank or 0 to turn a reminder off.")}

${s.map(g=>`
`).join("")}
`:"";return`${o} ${c}
${this._t("hdr.maintenance_log",{},"Maintenance Log")}

${this._t("msg.maintenance_intro",{},"Log servicing you perform on this appliance and get reminded when each task is due again.")}

${_}
${p}`}_htmlPanel(){const t=this._canEdit(),e=t&&this._constants&&this._constants.mlTrainingAvailable,s=new Set(["maintenance"]);t&&s.add("diagnostics"),e&&s.add("ml");let r=this._panelSubtab;s.has(r)||(r=this._panelSubtab="maintenance");const n=[["maintenance",this._t("tab.maintenance",{},"Maintenance")]];t&&n.push(["diagnostics",this._t("tab.diagnostics",{},"Diagnostics")]),e&&n.push(["ml",this._t("tab.ml",{},"ML Training")]);const a=n.map(([i,l])=>``).join(""),o=r==="diagnostics"&&t?this._htmlDiagnostics():r==="ml"&&e?this._htmlMlTab():this._htmlMaintenance();return`
${a}
${o}`}_levelSelect(t,e,s){const r=(s?[["inherit",this._t("access.inherit",{},"Inherit")]]:[]).concat([["none",this._t("access.none",{},"None (hidden)")],["read",this._t("access.read",{},"Read")],["edit",this._t("access.edit",{},"Edit")],["full",this._t("access.full",{},"Full")]]);return``}_htmlPanelPrefs(){const t=this._panelCfg&&this._panelCfg.prefs||{},e=this._hass&&this._hass.locale&&this._hass.locale.language||"en",r=[["",this._t("pref.use_panel_default",{},"(panel default)")],["status",this._t("tab.status",{},"Overview")],["history",this._t("tab.history",{},"Cycles")],["profiles",this._t("tab.profiles",{},"Profiles")],["settings",this._t("tab.settings",{},"Settings")],["playground",this._t("tab.playground",{},"Playground")]].map(([l,c])=>``).join(""),a=[["relative",this._t("pref.date_relative",{},"Relative (e.g. 2 hours ago)")],["absolute",this._t("pref.date_absolute",{},"Absolute (e.g. 14:32 on 2 Jul)")]].map(([l,c])=>``).join(""),o=t.lang_override||"",i=[["",this._t("pref.lang_auto",{lang:e.toUpperCase()},"System default ("+e.toUpperCase()+")")],["en",this._t("pref.lang_en",{},"English")]].map(([l,c])=>``).join("");return`
${this._t("hdr.my_preferences",{},"My Preferences")}

${this._t("msg.prefs_personal",{},"These apply to your Home Assistant account only.")}

${this._t("hdr.display",{},"Display")}
${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")}
${Et(`id="wd-pref-expected" ${t.show_expected!==!1?"checked":""}`,this._t("lbl.show_expected",{},"Show expected curve overlay (matched profile, orange)"))} ${Et(`id="wd-pref-raw" ${t.show_raw?"checked":""}`,this._t("lbl.show_raw",{},"Show raw socket toggle in live power graph"))}
${this._t("hdr.diagnostics_pref",{},"Diagnostics")}
${Et(`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)"))}
`}_htmlPanelSettings(){const t=this._panelCfg&&this._panelCfg.panel||{},e=[["status",this._t("tab.status",{},"Overview")],["history",this._t("tab.history",{},"Cycles")],["profiles",this._t("tab.profiles",{},"Profiles")],["settings",this._t("tab.settings",{},"Settings")],["playground",this._t("tab.playground",{},"Playground")]],s=e.map(([a,o])=>``).join(""),r=t.hidden_tabs||[],n=e.filter(([a])=>a!=="status").map(([a,o])=>Nt(`data-hidetab="${a}" ${r.includes(a)?"checked":""}`,o)).join("");return`
${this._t("hdr.panel_settings",{},"Panel Settings (all users)")}
${n}
`}_htmlPanelAccess(){const t=this._panelCfg&&this._panelCfg.rbac||{enabled:!1,default_level:"none",users:{}},e=this._panelCfg&&this._panelCfg.users||[],s=this._devices||[],r=e.filter(a=>!a.is_admin).map(a=>{const o=(t.users||{})[a.id]||{default:"none",devices:{}},i=s.map(l=>`
${d(l.title)}${this._levelSelect(`data-rbacuser="${d(a.id)}" data-rbacdev="${d(l.entry_id)}"`,(o.devices||{})[l.entry_id]||"inherit",!0)}
`).join("");return`
${d(a.name)}
${this._t("lbl.default_other",{},"Default (other devices)")}${this._levelSelect(`data-rbacuser="${d(a.id)}" data-rbacdev="__default__"`,o.default||"none",!1)}
${i}
`}).join(""),n=e.filter(a=>a.is_admin).map(a=>`${d(a.name)} - full (admin)`).join(" ");return`
${this._t("hdr.access_control",{},"Access Control")}
${Et(`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._levelSelect('id="wd-rbac-default"',t.default_level||"none",!1)}
${n?`
${n}
`:""}
${r||`

${this._t("msg.no_other_users",{},"No other Home Assistant users found.")}

`}`}_htmlStore(){if(!this._canEdit())return`
${this._t("msg.no_device_selected",{},"No device selected.")}
`;const t=this._storeStatus,e=`${this._t("store.website",{},"Store website \u2197")}`,s=`
${this._t("hdr.community_store",{},"Community Store")}
${e}
`;if(!this._onlineEnabled()||t&&t.enabled===!1)return`
${s}

${this._t("msg.store_enable_hint",{},"Enable online features in Settings to browse and import community reference cycles.")}

`;let r;return this._storeView==="device"?r=this._htmlStoreDevice():this._storeView==="profile"?r=this._htmlStoreProfile():r=this._htmlStoreBrands(),`
${s} ${this._htmlStoreCrumbs()} ${r}
`}_htmlStoreCrumbs(){const t=[``];if(this._storeDevice){const e=this._storeDevice,s=`${e.brand||""} ${e.model||""}`.trim()||this._t("store.device",{},"Device");t.push('\u203A'),t.push(this._storeView==="device"?`${d(s)}`:``)}return this._storeProfile&&this._storeView==="profile"&&(t.push('\u203A'),t.push(`${d(this._storeProfile.program||"")}`)),`
${t.join("")}
`}_htmlStoreLoading(){return`
\u23F3
${this._t("msg.loading",{},"Loading\u2026")}
`}_htmlStoreBrands(){const t=this._storeDevices||[],e=String((this._opts||{}).store_model||"").trim().toLowerCase(),s=t.map(o=>{const i=`${d(o.brand||"")} ${d(o.model||"")}`.trim()||this._t("store.device",{},"Device"),l=o.applianceType?`${d(this._deviceTypeLabel(o.applianceType))}`:"",_=e&&String(o.model||"").toLowerCase()===e?`${this._t("store.your_model",{},"Yours")}`:"",p=Number(o.profileCount)||0,g=p>0?`${this._t("store.programs_count",{n:p},`Programs: ${p}`)}`:"";return``}).join("");if(!this._storeBrandScope())return`

${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.")}

`,n=this._storeLoading?this._htmlStoreLoading():t.length?`
${s}
`:r,a=!this._storeLoading&&t.length>1?`

${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.")}

`:"";return` ${a} ${n}`}_htmlStoreDevice(){const t=this._storeProfiles||[],e=t.map(o=>``).join(""),s=this._storeLoading?this._htmlStoreLoading():t.length?`
${e}
`:`

${this._t("store.no_programs",{},"No shared programs for this appliance yet.")}

`,r=this._storeDevice,n=this._busy.has("store-download-device");return(this._canEdit()&&r&&t.length?`
${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.")} ${Nt(`data-action="store-toggle-dl-settings" ${this._dlSettings?"checked":""} ${n?"disabled":""}`,this._t("lbl.adopt_settings",{},"Also adopt settings"),it(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.")))}
`:"")+s}_htmlStoreProfile(){const t=this._storeCycles||[],e=t.map(r=>{const n=r.stats||{},a=this._storeSparkline(r.trace&&r.trace.points||[]),o=n.duration!=null?ot(n.duration):"-",i=n.energy_wh!=null?gt(n.energy_wh/1e3):"-",l=n.peak_w!=null?Ut(n.peak_w):"-",c=d(r.uploaderName||this._t("store.anon",{},"anonymous")),_=this._statusTag(r),p=r.rating||{},g=p.avg!=null&&p.count?this._t("store.rating_summary",{avg:Number(p.avg).toFixed(1),n:p.count},`\u2605 ${Number(p.avg).toFixed(1)} (${p.count})`):this._t("store.no_ratings",{},"No ratings yet");return`
${a}
${o} \xB7 ${i} \xB7 ${l} ${_}
${this._t("store.uploaded_by",{name:c},`Shared by ${c}`)} \xB7 \u2B07 ${r.downloads||0} \xB7 ${g}
`}).join("");return`
${this._storeLoading?this._htmlStoreLoading():t.length?e:`

${this._t("store.no_cycles",{},"No reference cycles shared for this program yet.")}

`}
`}_storeSparkline(t){const e=Array.isArray(t)?t.filter(u=>Array.isArray(u)&&u.length>=2):[];if(e.length<2)return'';const s=e.map(u=>u[0]),r=e.map(u=>u[1]),n=Math.min(...s),a=Math.max(...s),o=Math.max(1,...r),i=120,l=36,c=2,_=u=>c+(a>n?(u-n)/(a-n):0)*(i-2*c),p=u=>l-c-Math.max(0,u)/o*(l-2*c),g=e.map(u=>`${_(u[0]).toFixed(1)},${p(u[1]).toFixed(1)}`).join(" ");return``}_htmlGearModal(t){const e=this._isAdmin(),s=[["prefs",this._t("hdr.my_preferences",{},"My Preferences")]];e&&s.push(["panel",this._t("hdr.panel_settings",{},"Panel Settings")],["access",this._t("hdr.access_control",{},"Access Control")]),e&&this._constants&&this._constants.storeOnlineAvailable&&s.push(["online",this._t("hdr.online_account",{},"Online & Community")]);let r=t.tab;s.some(([o])=>o===r)||(r=t.tab="prefs");const n=s.map(([o,i])=>``).join(""),a=r==="panel"&&e?this._htmlPanelSettings():r==="access"&&e?this._htmlPanelAccess():r==="online"&&e?this._htmlOnlineSettings():this._htmlPanelPrefs();return`

${this._t("settings.gear.title",{},"Settings")}

${n}
${a}
`}_htmlOnlineSettings(){if(!(this._constants&&this._constants.storeOnlineAvailable))return`

${this._t("msg.online_unavailable",{},"Online features are not available on this server.")}

`;const t=this._onlineEnabled(),e=this._storeStatus||{},s=!!(t&&e.connected),r=this._busy.has("store-account"),n=t?s?`
${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.")}

${Et(`data-action="store-toggle-online" ${t?"checked":""} ${r?"disabled":""}`,this._t("lbl.enable_online",{},"Enable online features"))} ${t?this._htmlStorePrefs(r):""} ${t?n:""}
`}_htmlStorePrefs(t){const e=this._constants&&this._constants.storePrefs||{},s=re.map(n=>{const a=e[n.key]!==!1;return Et(`data-action="store-toggle-pref" data-pref="${d(n.key)}" ${a?"checked":""} ${t?"disabled":""}`,this._t(n.labelKey,{},n.labelFb),it(this._t(n.docKey,{},n.docFb)))}).join(""),r=this._busy.has("store-refresh-catalog");return s+`
${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.")}
`}async _loadStoreStatus(t){if(!this._onlineEnabled()){this._storeStatus={enabled:!1},this._storeConnected=!1;return}try{const e=await this._ws({type:`${y}/store_status`,entry_id:t});if(!this._isActiveEntry(t))return;this._storeStatus=e||null,this._storeConnected=!!(e&&e.connected)}catch{}}_storeBrandScope(){return String(this._storeQuery||(this._opts||{}).store_brand||"").trim()}async _storeSearch(t){const e=this._devices[this._selIdx];if(!e)return;const s=e.entry_id;this._storeQuery=t||"",this._storeView="brands",this._storeDevice=null,this._storeProfile=null,this._storeProfiles=[],this._storeCycles=[];const r=this._storeBrandScope();if(!r){this._storeDevices=[],this._storeLoading=!1,this._render();return}this._storeLoading=!0,this._render();try{const n=await this._ws({type:`${y}/store_search_devices`,entry_id:s,query:r,appliance_type:this._storeApplianceType(),include_pending:!0});if(!this._isActiveEntry(s))return;n&&n.disabled?(this._storeStatus={enabled:!1},this._storeDevices=[]):this._storeDevices=this._sortStoreDevices(n&&n.items||[])}catch(n){this._isActiveEntry(s)&&(this._storeDevices=[],this._showToast(this._t("toast.store_search_failed",{error:n.message||n},"Search failed: "+(n.message||n)),"error"))}finally{this._isActiveEntry(s)&&(this._storeLoading=!1,this._render())}}_sortStoreDevices(t){const e=String((this._opts||{}).store_model||"").trim().toLowerCase(),s=r=>e&&String(r.model||"").toLowerCase()===e?0:this._storeItemHasContent(r)?1:2;return t.map((r,n)=>({d:r,i:n})).sort((r,n)=>s(r.d)-s(n.d)||r.i-n.i).map(r=>r.d)}_storeItemHasContent(t){return(Number(t&&t.profileCount)||0)>0||(Number(t&&t.cycleCount)||0)>0}_ensureStoreConnectListener(){if(this._storeConnectListener)return;const t=this._constants.storeWebOrigin;if(!t)return;let e;try{e=new URL(t).origin}catch{return}this._storeConnectListener=async s=>{if(s.origin!==e)return;const r=s.data;if(!r)return;const n=this._devices[this._selIdx];if(!n)return;const a=n.entry_id;if(r.type==="washdata-device-created"){const o={};r.brand&&(o.store_brand=r.brand),r.model&&(o.store_model=r.model),this._opts={...this._opts,...o},this._catalog.brands=void 0,this._catalog.devices=void 0,this._catalog.forBrand=null,this._catalog.brandsFull=!1,this._catalog.brandPrefixes=[],this._catalogEntry=null,this._showToast(this._t("toast.appliance_added",{},"Appliance added - awaiting approval")),this._render();return}if(r.type==="washdata-brand-created"){r.brand&&(this._opts={...this._opts,store_brand:r.brand}),this._catalog.brands=void 0,this._catalog.brandsFull=!1,this._catalog.brandPrefixes=[],this._catalogEntry=null,this._showToast(this._t("toast.brand_added",{},"Brand added - awaiting approval")),this._render();return}if(r.type==="washdata-profile-created"){const o=this._modal;o&&o.type==="store-share"&&(o.program=r.program||o.program,this._loadShareProfiles()),this._showToast(this._t("toast.profile_added",{},"Profile added - awaiting approval"));return}if(r.type==="washdata-connect")try{const o=await this._ws({type:`${y}/store_connect`,entry_id:a,refresh_token:r.refreshToken,uid:r.uid,name:r.displayName});if(o&&o.error){this._showToast(this._t("toast.store_connect_failed",{error:o.error},"Connect failed: "+o.error),"error");return}if(await this._loadStoreStatus(a),!this._isActiveEntry(a))return;this._showToast(this._t("toast.store_connected",{},"Connected to the community store")),this._render()}catch(o){this._showToast(this._t("toast.store_connect_failed",{error:o.message||o},"Connect failed: "+(o.message||o)),"error")}},window.addEventListener("message",this._storeConnectListener)}async _saveStoreOptions(t){const e=this._devices[this._selIdx];if(!e)return!1;const s=e.entry_id;try{return await this._ws({type:`${y}/set_options`,entry_id:s,options:t}),this._opts={...this._opts,...t},this._showToast(this._t("toast.settings_saved",{},"Settings saved; integration reloading")),!0}catch(r){return this._showToast(this._t("msg.toast_save_failed",{error:r.message||r},"Save failed: "+(r.message||r)),"error"),!1}}_htmlLogDrawer(){return`
${this._t("hdr.logs",{},"Logs")}
${this._htmlLogFilters("drawer")}

${this._t("msg.log_buffer_hint",{},"Newest first \xB7 buffers the last 500 ha_washdata records since restart \xB7 drag the left edge to resize.")}

${this._logLinesHtml()}
`}_drawCurves(t,e){const s=this.shadowRoot&&this.shadowRoot.getElementById(t);if(!s)return null;const r=s.getBoundingClientRect(),n=window.devicePixelRatio||1,a=Math.max(1,Math.round(r.width*n)),o=Math.max(1,Math.round((r.height||240)*n));(s.width!==a||s.height!==o)&&(s.width=a,s.height=o);const i=s.getContext("2d"),l=getComputedStyle(this),c=(l.getPropertyValue("--primary-color")||"#03a9f4").trim()||"#03a9f4",_=(l.getPropertyValue("--divider-color")||"rgba(127,127,127,.3)").trim()||"rgba(127,127,127,.3)",p=(l.getPropertyValue("--secondary-text-color")||"#888").trim()||"#888",g=44*n,u=12*n,v=12*n,b=22*n,$=e.series||[];let k=e.xMax||0;k||($.forEach(A=>(A.points||[]).forEach(E=>{E[0]>k&&(k=E[0])})),e.band&&(e.band.max||[]).forEach(A=>{A[0]>k&&(k=A[0])})),k=k||1;let S=e.yMax||0;if(!S){const A=E=>(E||[]).forEach(M=>{M[1]>S&&(S=M[1])});$.forEach(E=>{E.noScale||A(E.points)}),e.band&&A(e.band.max),S=(S||10)*1.08}const C=this._canvasZoom&&this._canvasZoom[t],R=C?C.xMin:0,B=C?C.xMax:k,W=a-g-u,O=A=>g+(A-R)/(B-R)*W,D=A=>o-b-A/S*(o-v-b);i.clearRect(0,0,a,o),i.strokeStyle=_,i.lineWidth=n,i.fillStyle=p,i.font=`${11*n}px sans-serif`,i.textAlign="right",i.textBaseline="middle";for(let A=0;A<=2;A++){const E=v+A/2*(o-v-b);i.beginPath(),i.moveTo(g,E),i.lineTo(a-u,E),i.stroke(),i.fillText(Math.round(S*(1-A/2))+"W",g-4*n,E)}if(i.save(),i.beginPath(),i.rect(g,v,W,o-v),i.clip(),(e.bands||[]).forEach(A=>{i.fillStyle=A.fill;const E=O(A.x0),M=O(A.x1);i.fillRect(Math.min(E,M),v,Math.abs(M-E),o-v-b)}),e.band&&(e.band.min||[]).length&&(e.band.max||[]).length){i.beginPath(),e.band.max.forEach((A,E)=>E?i.lineTo(O(A[0]),D(A[1])):i.moveTo(O(A[0]),D(A[1])));for(let A=e.band.min.length-1;A>=0;A--)i.lineTo(O(e.band.min[A][0]),D(e.band.min[A][1]));i.closePath(),i.fillStyle=e.band.fill||St(c,.13),i.fill()}return $.forEach(A=>{const E=A.points||[];if(!E.length)return;const M=A.stroke==="primary"?c:A.stroke;if(A.fill){i.beginPath(),E.forEach((q,Q)=>Q?i.lineTo(O(q[0]),D(q[1])):i.moveTo(O(q[0]),D(q[1]))),i.lineTo(O(E[E.length-1][0]),D(0)),i.lineTo(O(E[0][0]),D(0)),i.closePath();const H=i.createLinearGradient(0,v,0,o-b);H.addColorStop(0,St(M,.33)),H.addColorStop(1,St(M,.03)),i.fillStyle=H,i.fill()}i.beginPath(),E.forEach((H,q)=>q?i.lineTo(O(H[0]),D(H[1])):i.moveTo(O(H[0]),D(H[1]))),i.strokeStyle=M,i.lineWidth=(A.width||1.5)*n,i.lineJoin="round",A.dash&&i.setLineDash([6*n,4*n]),i.globalAlpha=A.alpha!=null?A.alpha:1,i.stroke(),i.globalAlpha=1,A.dash&&i.setLineDash([])}),i.textAlign="center",i.textBaseline="top",(e.vlines||[]).forEach(A=>{const E=O(A.x);i.beginPath(),i.moveTo(E,v),i.lineTo(E,o-b),i.strokeStyle=A.color,i.lineWidth=2*n,i.setLineDash([4*n,3*n]),i.stroke(),i.setLineDash([]),A.handle&&(i.fillStyle=A.color,i.beginPath(),i.arc(E,v+4*n,4.5*n,0,Math.PI*2),i.fill()),A.label&&(i.fillStyle=A.color,i.fillText(A.label,E,v+(A.handle?12*n:2*n)))}),i.restore(),i.fillStyle=p,i.font=`${11*n}px sans-serif`,i.textBaseline="bottom",C&&(i.textAlign="left",i.fillText((R/60).toFixed(1)+" min",g,o-2*n)),i.textAlign="right",i.fillText((B/60).toFixed(0)+" min",a-u,o-2*n),s._wd={xMax:k,xMin:R,xViewMax:B,yMax:S,dpr:n,padT:v,padB:b,ch:o,primary:c,Xpx:O,Ypx:D,xToCss:A=>O(A)/n,cssToX:A=>Math.max(R,Math.min(B,R+(A*n-g)/W*(B-R))),series:(e.series||[]).map(A=>({points:A.points,stroke:A.stroke,name:A.name,cid:A.cid})),band:e.band||null,artifacts:e.artifacts||null,_opts:e},s._wd}_drawModalCanvas(){const t=this._modal;t&&(t.type==="cycle-detail"?this._drawCycleEditor():t.type==="compare-cycles"?this._drawCompareCanvas():t.type==="profile-group"?this._drawGroupCanvas():t.type==="profile-panel"&&(t.tab==="stats"?this._drawProfileEnvelope():t.tab==="phases"?this._drawPhaseEditor():t.tab==="cleanup"&&this._drawSpaghetti()))}_redrawCanvas(t){if(t==="wd-status-canvas")this._drawStatusCurve();else if(t==="wd-cyc-canvas")this._drawCycleEditor();else if(t==="wd-compare-canvas")this._drawCompareCanvas();else if(t==="wd-env-canvas")this._drawProfileEnvelope();else if(t==="wd-phase-canvas")this._drawPhaseEditor();else if(t==="wd-spag-canvas")this._drawSpaghetti();else if(t==="wd-pgroup-canvas")this._drawGroupCanvas();else{const e=this.shadowRoot&&this.shadowRoot.getElementById(t);e&&e._wd&&e._wd._opts&&this._drawCurves(t,e._wd._opts)}}_pref(t,e){const s=this._panelCfg&&this._panelCfg.prefs||{};return s[t]===void 0?e:s[t]}_setPref(t,e){this._panelCfg||(this._panelCfg={}),this._panelCfg.prefs={...this._panelCfg.prefs||{},[t]:e},this._ws({type:`${y}/set_user_prefs`,prefs:{[t]:e}}).catch(()=>{})}_drawStatusCurve(){const t=this._powerData||{},e=t.live||[];if(e.length<2)return;const s=this._statusEnv,r=this._pref("show_expected",!0),n=this._pref("show_raw_active",!1),a=[];let o=e[e.length-1][0];if(t.cycle_active&&s&&(s.avg||[]).length&&r){const l=s.target_duration||s.avg[s.avg.length-1][0];a.push({points:s.avg,stroke:"#ff9800",width:2,alpha:.4,name:this._t("lbl.expected",{},"Expected")}),o=Math.max(o,l)}a.push({points:e,stroke:"primary",fill:!0,width:2,name:this._t("lbl.power",{},"Power")}),n&&(t.raw||[]).length>1&&a.push({points:t.raw,stroke:"#9e9e9e",width:1,alpha:.65,name:this._t("lbl.raw_socket",{},"Raw socket"),noScale:!0});const i=[];(t.restart_gaps||[]).forEach(l=>{if(!e.length)return;const c=t.cycle_start_iso;if(!c)return;const _=new Date(c).getTime(),p=Math.max(0,(new Date(l.start_ts).getTime()-_)/1e3),g=Math.max(p+1,(new Date(l.end_ts).getTime()-_)/1e3);i.push({x0:p,x1:g,fill:"rgba(96,125,139,.20)"})}),this._drawCurves("wd-status-canvas",{series:a,xMax:o,bands:i})}_attachHover(t){const e=this.shadowRoot,s=e&&e.getElementById(t);s&&(s.addEventListener("pointermove",r=>this._onGraphHover(r,t)),s.addEventListener("pointerleave",()=>this._hideGraphTip()),s.addEventListener("wheel",r=>{const n=s._wd;if(!n)return;r.preventDefault();const a=s.getBoundingClientRect(),o=n.cssToX(r.clientX-a.left),i=this._canvasZoom[t],l=n.xMax,c=i?i.xMin:0,p=(i?i.xMax:l)-c,g=p*(r.deltaY>0?1.3:.75);if(g>=l*.99)delete this._canvasZoom[t];else{const u=(o-c)/p,v=Math.min(l,o-u*g+g),b=Math.max(0,v-g);this._canvasZoom[t]={xMin:b,xMax:Math.min(l,b+g)}}this._redrawCanvas(t)},{passive:!1}),s.addEventListener("dblclick",()=>{delete this._canvasZoom[t],this._redrawCanvas(t)}))}_onGraphHover(t,e){const s=t.clientX,r=t.clientY;if(this._hoverRafId){this._hoverPending={px:s,py:r,id:e};return}this._hoverPending={px:s,py:r,id:e},this._hoverRafId=requestAnimationFrame(()=>{this._hoverRafId=null;const{px:n,py:a,id:o}=this._hoverPending||{};o&&this._onGraphHoverInner(n,a,o)})}_onGraphHoverInner(t,e,s){const r=this.shadowRoot&&this.shadowRoot.getElementById(s),n=r&&r._wd;if(!n)return;const a=r.getBoundingClientRect(),o=n.cssToX(t-a.left),i=(e-a.top)*n.dpr;this._redrawCanvas(s);const l=r.getContext("2d"),c=n.Xpx(o);l.save(),l.strokeStyle="rgba(140,140,140,.75)",l.lineWidth=n.dpr,l.setLineDash([3*n.dpr,3*n.dpr]),l.beginPath(),l.moveTo(c,n.padT),l.lineTo(c,n.ch-n.padB),l.stroke(),l.setLineDash([]);const _=v=>v.stroke==="primary"?n.primary:v.stroke,p=(v,b)=>{l.fillStyle=b,l.beginPath(),l.arc(c,n.Ypx(v),3.4*n.dpr,0,6.2832),l.fill()},g=[`${this._t("lbl.from_start",{},"From start")}: ${Mt(o)}`,`${this._t("lbl.to_end",{},"To end")}: ${Mt(Math.max(0,n.xMax-o))}`],u=n.series||[];if(this._hoverNearest=null,u.length>4){let v=null,b=1/0;if(u.forEach($=>{const k=Dt($.points,o);if(k==null)return;const S=Math.abs(n.Ypx(k)-i);SS?l.lineTo(n.Xpx(k[0]),n.Ypx(k[1])):l.moveTo(n.Xpx(k[0]),n.Ypx(k[1]))),l.stroke(),p(v.v,$),g.push(`${d(v.s.name||"")}: ${v.v.toFixed(v.v<100?1:0)} W`),v.s.cid&&(g.push(`${this._t("lbl.click_to_select",{},"click to select")}`),this._hoverNearest={id:s,cid:v.s.cid})}}else u.forEach(v=>{const b=Dt(v.points,o);b!=null&&(p(b,_(v)),g.push(`${d(v.name||this._t("lbl.power",{},"Power"))}: ${b.toFixed(b<100?1:0)} W`))});if(n.band){const v=Dt(n.band.min,o),b=Dt(n.band.max,o);v!=null&&b!=null&&g.push(`${this._t("lbl.envelope",{},"Envelope")}: ${v.toFixed(0)}\u2013${b.toFixed(0)} W`)}(n.artifacts||[]).forEach(v=>{if(o>=v.start_s&&o<=v.end_s){const b=v.detail_key?this._t(v.detail_key,v.detail_params||{},v.detail||""):v.detail||"";g.push(`\u26A0 ${d(Zt(v.type,($,k,S)=>this._t($,k,S)))}: ${d(b)}`)}}),this._canvasZoom[s]&&g.push(`${this._t("lbl.zoom_hint",{},"scroll to zoom \xB7 dblclick to reset")}`),l.restore(),this._showGraphTip(t,e,g),this._syncSpagRowHighlight(this._hoverNearest?this._hoverNearest.cid:null)}_showGraphTip(t,e,s){const r=this._gtip;if(!r)return;r.innerHTML=s.join("
"),r.style.display="block";const n=r.offsetWidth,a=r.offsetHeight,o=16;let i=t+o,l=e+o;i+n>window.innerWidth-6&&(i=t-n-o),l+a>window.innerHeight-6&&(l=e-a-o),r.style.left=Math.max(6,i)+"px",r.style.top=Math.max(6,l)+"px"}_hideGraphTip(){this._hoverRafId&&(cancelAnimationFrame(this._hoverRafId),this._hoverRafId=null,this._hoverPending=null),this._gtip&&(this._gtip.style.display="none"),this._syncSpagRowHighlight(null)}_positionTip(t){const e=t.querySelector(".wd-tip-pop");if(!e)return;e.style.transform="";const s=!e.offsetWidth,r=s?e.style.cssText:"";s&&(e.style.visibility="hidden",e.style.display="block");const n=e.getBoundingClientRect();if(s&&(e.style.cssText=r),!n.width)return;const a=ue(t),o=6,i=a.left+o,l=a.right-o-n.width,c=l{s.style.backgroundColor=t&&s.dataset.cid===t?"var(--secondary-background-color,rgba(0,0,0,.06))":""})}_showToast(t,e="success",s={}){this._toastTimer&&clearTimeout(this._toastTimer);const r=s.duration||3500;this._toast={msg:t,cls:`wd-toast-${e}`,actionLabel:s.actionLabel||null,actionToken:s.actionToken||null},this._render(),this._toastTimer=setTimeout(()=>{this._toast=null,this._render()},r)}_profileOptions(t){return(this._profiles||[]).map(e=>``).join("")}_htmlModal(){const t=this._modal;if(t.type==="cycle-detail")return`
`;if(t.type==="profile-panel")return`
`;if(t.type==="profile-group")return`
`;if(t.type==="compare-cycles")return`
`;if(t.type==="gear-settings")return`
`;if(t.type==="export-select")return`
`;if(t.type==="import-wizard")return`
`;if(t.type==="history-import")return`
`;let e="";if(t.type==="confirm")e=`

${d(t.title)}

${d(t.message)}

`;else if(t.type==="label-cycle")e=`

${this._t("modal.label_cycle",{},"Label Cycle")}

`;else if(t.type==="create-profile"){const s=(this._cycles||[]).slice(0,40).map(r=>``).join("");e=`

${this._t("modal.create_profile",{},"Create Profile")}

${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.")}
`}else if(t.type==="create-phase")e=`

${this._t("modal.new_phase",{},"New Phase")}

`;else if(t.type==="edit-phase"){const s=t.isDefault?`

${this._t("msg.edit_builtin_phase",{},"This is a built-in phase. Saving creates a custom override \u2014 the original is preserved and can be restored by deleting the override.")}

`:"";e=`

${this._t("modal.edit_phase",{},"Edit Phase")} ${t.isDefault?`${this._t("badge.built_in_tag",{},"built-in")}`:""}

${s}
`}else if(t.type==="process-recording")e=`

${this._t("modal.process_recording",{},"Process Recording")}

${d(this._t("msg.head_trim_hint",{},"Remove this many seconds from the start"))}
${d(this._t("msg.tail_trim_hint",{},"Remove this many seconds from the end"))}
`;else if(t.type==="correct-feedback")e=`

${this._t("modal.correct_feedback",{},"Correct Feedback")}

WashData detected: ${d(t.detectedProfile)}

`;else if(t.type==="import-config")e=`

${this._t("modal.import_config",{},"Import Configuration")}

${this._t("msg.import_intro",{},"Load an exported file or paste a JSON payload below.")}

`;else if(t.type==="auto-label")e=`

${this._t("modal.auto_label",{},"Auto-Label Cycles")}

${this._t("msg.auto_label_intro",{},"Assign profiles to unlabelled cycles whose match confidence clears the threshold.")}

`;else if(t.type==="merge-cycles")e=`

${this._t("modal.merge_cycles",{n:t.ids.length},`Merge ${t.ids.length} Cycles`)}

${this._t("msg.merge_intro",{},"The selected cycles are combined into one (chronological order; gaps filled with 0 W). Pick the resulting profile.")}

`;else if(t.type==="bulk-relabel")e=`

${this._t("modal.relabel_cycles",{count:t.ids.length},`Relabel ${t.ids.length} cycles`)}

`;else if(t.type==="store-import"){const s=t.mode!=="merge",r=``;e=`

${this._t("modal.store_import",{},"Import reference cycle")}

${s?`
`:`
${r}
`}
`}else if(t.type==="store-share"){const s=Array.isArray(t.profiles)?t.profiles:[],r=[],n=new Set,a=l=>{const c=(l||"").toLowerCase();l&&!n.has(c)&&(n.add(c),r.push(l))};a(t.program),s.forEach(l=>a(l.program));const o=t.profiles==null?` ${this._t("msg.loading",{},"Loading\u2026")}`:"",i=r.length?r.map(l=>``).join(""):``;e=`

${this._t("modal.store_share",{},"Share to community store")}

${this._t("msg.store_share_intro",{},"Upload this reference cycle so others with the same appliance can use it. It is reviewed before appearing publicly.")}

`}else t.type==="store-share-device"&&(e=this._htmlShareDeviceModal(t));return`
`}_htmlShareDeviceModal(t){const e=this._shareableByProgram(),s=this._busy.has("store-share-device"),r=t.selected||new Set,n=!!t.consented,a=e.reduce((u,v)=>u+v.cycles.filter(b=>r.has(b.id)).length,0),o=e.some(u=>!u.noCycles);let i;t.loading?i=`
\u23F3
${this._t("msg.loading",{},"Loading\u2026")}
`:e.length?i=e.map(u=>{if(u.noCycles)return`
${d(u.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")}
`;const v=u.cycles.every(C=>r.has(C.id)),b=!v&&u.cycles.some(C=>r.has(C.id)),$=u.cycles.map(C=>{const R=C.start_time?st(C.start_time):"",B=C.duration!=null?ot(C.duration):"";return``}).join(""),S=(this._sharePhasePrograms||[]).includes(u.program)?``:"";return`
${$}
${S}
`}).join(""):i=`
\u{1F4E4}
${this._t("msg.share_device_none",{},"No shareable cycles yet. Mark a recorded or hand-picked cycle as a reference cycle (\u2B50) in the Cycles tab first.")}
`;const l=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.")}
`,g=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.`)}

${p}
${i}
${o?_:""} ${g}
`}_wizCatOrder(){return["profiles","real_cycles","reference_cycles","custom_phases","profile_groups","settings","matching_config","ml_models","feedback","suggestions","maintenance_log","history_logs","lifetime_stats"]}_wizCatLabel(t){return{profiles:this._t("lbl.cat_profiles",{},"Profiles (programs)"),real_cycles:this._t("lbl.cat_real_cycles",{},"Cycles (run history)"),reference_cycles:this._t("lbl.cat_reference_cycles",{},"Reference cycles (imported)"),custom_phases:this._t("lbl.cat_custom_phases",{},"Custom phases"),profile_groups:this._t("lbl.cat_profile_groups",{},"Profile groups"),settings:this._t("lbl.cat_settings",{},"Detection & matching settings"),matching_config:this._t("lbl.cat_matching_config",{},"Matcher tuning"),ml_models:this._t("lbl.cat_ml_models",{},"ML models"),feedback:this._t("lbl.cat_feedback",{},"Feedback & review labels"),suggestions:this._t("lbl.cat_suggestions",{},"Suggestions"),maintenance_log:this._t("lbl.cat_maintenance_log",{},"Maintenance log"),history_logs:this._t("lbl.cat_history_logs",{},"History & change logs"),lifetime_stats:this._t("lbl.cat_lifetime_stats",{},"Lifetime totals")}[t]||t}_wizInitSel(t,e){const s=new Set,r=new Set,n=new Set,a=new Set,o=t&&t.categories||{},i=l=>!!(l&&l.present&&(!e||l.importable!==!1));return i(o.profiles)&&(o.profiles.items||[]).forEach(l=>r.add(l.name)),i(o.real_cycles)&&(o.real_cycles.groups||[]).forEach(l=>l.cycles.forEach(c=>{c.id!=null&&n.add(String(c.id))})),i(o.reference_cycles)&&(o.reference_cycles.groups||[]).forEach(l=>l.cycles.forEach(c=>{c.id!=null&&a.add(String(c.id))})),Object.keys(o).forEach(l=>{["profiles","real_cycles","reference_cycles"].includes(l)||i(o[l])&&s.add(l)}),{cats:s,profiles:r,realIds:n,refIds:a}}_wizSelectionPayload(t){const e=t.sel,s=[];e.profiles.size&&s.push("profiles"),e.realIds.size&&s.push("real_cycles"),e.refIds.size&&s.push("reference_cycles"),e.cats.forEach(n=>s.push(n));const r={categories:s};return e.profiles.size&&(r.profiles=Array.from(e.profiles)),e.realIds.size&&(r.real_cycle_ids=Array.from(e.realIds)),e.refIds.size&&(r.reference_cycle_ids=Array.from(e.refIds)),r}_wizGroupIds(t,e,s){const n=(((t.categories||{})[e]||{}).groups||[]).find(a=>a.profile===s);return n?n.cycles.filter(a=>a.id!=null).map(a=>String(a.id)):[]}_wizCatState(t,e,s){const r=t.sel,n=(s.categories||{})[e]||{};if(e==="profiles"){const o=n.items||[],i=o.length,l=o.filter(c=>r.profiles.has(c.name)).length;return{sel:l,total:i,state:l===0?"none":l===i?"all":"some"}}if(e==="real_cycles"||e==="reference_cycles"){const o=e==="real_cycles"?r.realIds:r.refIds,i=n.count||0;let l=0;return(n.groups||[]).forEach(c=>c.cycles.forEach(_=>{o.has(String(_.id))&&l++})),{sel:l,total:i,state:l===0?"none":l===i?"all":"some"}}const a=r.cats.has(e);return{sel:a?1:0,total:1,state:a?"all":"none"}}_htmlSelectionTree(t,e,s={}){const r=e&&e.categories||{},n=new Set;s.conflicts&&(r.profiles&&r.profiles.items||[]).forEach(_=>{_.conflict&&n.add(_.name)});const a=this._wizCatOrder().filter(_=>r[_]&&r[_].present);let o=!1,i=!0;a.forEach(_=>{if(s.importableOnly&&r[_].importable===!1)return;const p=this._wizCatState(t,_,e);p.state!=="none"&&(o=!0),p.state!=="all"&&(i=!1)});const l=``,c=a.map(_=>{const p=r[_],g=s.importableOnly&&p.importable===!1,u=this._wizCatState(t,_,e),b=["profiles","real_cycles","reference_cycles"].includes(_)?`${u.sel}/${p.count}`:"",$=``;let k="";if(!g&&_==="profiles")k=`
${(p.items||[]).map(S=>``).join("")}
`;else if(!g&&(_==="real_cycles"||_==="reference_cycles")){const S=_==="real_cycles"?t.sel.realIds:t.sel.refIds;k=`
${(p.groups||[]).map(C=>{const R=C.cycles.map(M=>String(M.id)),B=R.filter(M=>S.has(M)).length,W=B===R.length&&R.length>0,O=B>0&&!W,D=`${_}:${C.profile}`,A=t.expanded&&t.expanded.has(D),E=A?C.cycles.map(M=>``).join(""):"";return`
${E}
`}).join("")}
`}return`
${$}${k}
`}).join("");return`
${l}${c}
`}_htmlExportSelectModal(t){const e=this._busy.has("export-select");let s;t.loading||!t.inventory?s=`
\u23F3
${this._t("msg.loading",{},"Loading\u2026")}
`:s=this._htmlSelectionTree(t,{categories:t.inventory},{importableOnly:!1});const r=t.sel&&(t.sel.profiles.size||t.sel.realIds.size||t.sel.refIds.size||t.sel.cats.size);return`

${this._t("modal.export_select",{},"Export - choose data")}

${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).")}

${s}
`}_htmlImportWizardModal(t){const e=this._busy.has("import-wizard"),s=`

${this._t("modal.import_wizard",{},"Import - choose data")}

`;if(t.step==="input"||t.step==="analyze"){const $=t.step==="analyze";return`${s}

${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.")}

${t.error?`

${d(t.error)}

`:""}
`}const r=t.manifest||{},n=r.device_type_match===!1,a=d(r.source_device_type||"?"),o=d(r.local_device_type||"?"),i=n?`
${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.")}
`:"",l=this._htmlSelectionTree(t,r,{importableOnly:!0,conflicts:!0}),c=`
${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.")}
`,_=r.real_history_allowed!==!1,p=`
${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.")}
`;let g="";const v=(r.categories&&r.categories.profiles&&r.categories.profiles.items||[]).filter($=>$.conflict&&t.sel.profiles.has($.name));t.mode==="merge"&&v.length&&(g=`
${v.map($=>`
${d($.name)}
`).join("")}
`);const b=t.sel&&(t.sel.profiles.size||t.sel.realIds.size||t.sel.refIds.size||t.sel.cats.size);return`${s} ${i} ${l} ${c} ${p} ${g}
`}_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")}

`,s=t.error?`

${d(t.error)}

`:"";if(t.step==="input"){const $=this._busy.has("hist-import"),k=$?"disabled":"";return`${e}

${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.")}
${s}
`}if(t.step==="scan"){const $=t.scanTaskId?(this._tasks||{})[t.scanTaskId]:null,k=$&&$.total>0?Math.round($.done/$.total*100):null;return`${e}

${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.")}

${k==null?this._t("status.preparing",{},"Preparing\u2026"):`${k}%`}

${s}
`}if(t.step==="done"){const $=t.done||{},k=[this._t("msg.hist_imported_count",{n:$.imported||0},`${$.imported||0} cycles imported.`),$.duplicates?this._t("msg.hist_duplicates",{n:$.duplicates},`${$.duplicates} were already imported and were skipped.`):"",$.capped?this._t("msg.hist_capped",{},"The per-device limit for imported cycles was reached; the rest were not stored."):""].filter(Boolean);return`${e} ${k.map(S=>`

${d(S)}

`).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(`${st(a.first)} \u2013 ${st(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||[],_={};c.forEach($=>{_[$.reason]=(_[$.reason]||0)+1});const p=Object.entries(_).map(([$,k])=>`${k} \xD7 ${this._histSkipReason($)}`).join(", "),g=r.settings||{},u=g.min_power!=null?this._t("msg.hist_settings_used",{w:g.min_power,s:g.off_delay},`Detected using this device's current settings (minimum power ${g.min_power} W, off delay ${g.off_delay} s).`):"";if(!n.length)return`${e}

${this._t("msg.hist_none_found",{},"No cycles could be detected in that history.")}

${l.length?`

${d(l.join(" \xB7 "))}

`:""} ${p?`

${this._t("msg.hist_skipped_spans",{},"Skipped stretches")}: ${d(p)}

`:""} ${u?`

${d(u)}

`:""}
`;const v=n.map($=>{const k=i.has($.index),S=this._histSegReason($.reason);return` ${d(st($.start_time))} ${d(ot($.duration_s))} ${$.energy_wh!=null?d(($.energy_wh/1e3).toFixed(2))+" kWh":"\u2013"} ${d(String(Math.round($.peak_w)))} W ${S?`\u26A0 ${d(S)}`:`${d(this._t("lbl.hist_looks_complete",{},"complete"))}`} `}).join(""),b=n.every($=>i.has($.index));return`${e}

${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.`)}

${l.length?`

${d(l.join(" \xB7 "))}

`:""} ${p?`

${this._t("msg.hist_skipped_spans",{},"Skipped stretches")}: ${d(p)}

`:""} ${u?`

${d(u)}

`:""}
${v}
${this._t("lbl.date",{},"Date")} ${this._t("lbl.duration",{},"Duration")} ${this._t("lbl.energy",{},"Energy")} ${this._t("lbl.peak_power_short",{},"Peak")} ${this._t("lbl.shape",{},"Shape")} ${this._t("lbl.notes",{},"Notes")}
${r.capped?`

${this._t("msg.hist_scan_capped",{n:r.found},`Only the first candidates are shown (${r.found} were found).`)}

`:""} ${s}
`}_drawHistorySparklines(){const t=this.shadowRoot;if(!t)return;const e=this._modal,s=e&&e.result&&e.result.segments||[],r={};s.forEach(n=>{r[String(n.index)]=n.curve||[]}),t.querySelectorAll("canvas[data-hist-spark]").forEach(n=>{this._paintSparkline(n,r[n.dataset.histSpark]||[])})}_htmlCycleModal(t){if(!t.loaded)return`

${this._t("modal.cycle",{},"Cycle")}

\u23F3
${this._t("msg.loading_curve",{},"Loading curve\u2026")}
`;const e=t.curve||{},s=!!e.is_reference,r=e.labelable!==!1,n=e.editable!==!1,a=e.cycle_origin==="backfill",o=e.full_duration_s||e.duration||0,i=e.energy_kwh!=null?e.energy_kwh:null,l=t.ml||null;let c="";if(l&&l.ml_quality_score!=null){const E=l.ml_quality_label,M=E==="ok"?"var(--success-color,#4caf50)":E==="uncertain"?"var(--warning-color,#ff9800)":"var(--error-color,#f44336)",H=Math.round((1-l.ml_quality_score)*100);c=`
${H}%
${this._t("lbl.cycle_health",{},"Cycle health")}
`}const _=`
${ot(e.duration||o)}
${this._t("lbl.duration",{},"Duration")}
${gt(i)}
${this._t("lbl.energy",{},"Energy")}
${d(e.profile_name||this._t("lbl.unlabelled",{},"Unlabelled"))}
${this._t("lbl.profile",{},"Profile")}
${d(e.status||"-")}
${this._t("lbl.status",{},"Status")}
${c}
`,p=l&&l.ml_review||{},g=(this._feedbacks||[]).some(E=>E.cycle_id===t.cycleId),u=l&&l.ml_quality_label,v=!p.reviewed_at&&(g||["uncertain","review"].includes(u)||["force_stopped","interrupted"].includes(e.status)),b=v&&t.mode!=="review"?` \u25CF`:"",$=this._canEdit()&&n?`
`:s?`
\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.")}
`:"",k=this._canEdit()?(this._feedbacks||[]).find(E=>E.cycle_id===t.cycleId):null,S=k?k.detected_profile||k.profile_name||this._t("lbl.unknown",{},"Unknown"):"",C=k?`
\u26A0 ${this._t("msg.pending_feedback",{},"Pending detection feedback")}

${this._t("msg.unsure_detected_prefix",{},"WashData is unsure it detected")} ${d(S)}${k.confidence!=null?` (${this._t("lbl.confidence",{},"confidence").toLowerCase()} ${(k.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.")}

`:"";let R="";if(t.mode==="view"){const E=!!(l&&l.ml_review&&l.ml_review.golden),H=this._canEdit()&&this._onlineEnabled()&&this._storeConnected&&E?``:"",q=this._canEdit()?`${r?` `:""}`:"";R=`${C}
${H} ${q}
`}else if(t.mode==="trim"){const E=this._busy.has("cyc-trim-apply"),M=t.timeMode||"s",H=M==="clock"?this._offsetToClock(t.trim.start):Math.round(t.trim.start),q=M==="clock"?this._offsetToClock(t.trim.end):Math.round(t.trim.end),Q=M==="clock"?"time":"number",J=M==="clock"?'step="1"':`min="0" max="${Math.ceil(o)}" step="1"`,G=M==="clock"?"":" "+this._t("lbl.unit_s",{},"(s)");R=`

${this._t("msg.trim_intro",{},"Drag the red handles, or enter values. Everything outside the window is removed.")}

${this._t("lbl.input",{},"Input:")}
`}else if(t.mode==="split"){const E=this._busy.has("cyc-split-apply"),M=(t.split.offsets||[]).slice().sort((Q,J)=>Q-J),H=[0,...M,o],q=H.slice(0,-1).map((Q,J)=>{const G=H[J+1];return`
${ot(Q)} \u2013 ${ot(G)}
`}).join("");R=`

${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.")}

${M.length?q:`

${this._t("msg.no_split_points",{},"No split points yet.")}

`}
`}else if(t.mode==="review"){const E=l&&l.ml_review||{},M=this._busy.has("cyc-review-save"),H=(f,m)=>``,Q=[["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(([f,m])=>``).join(""),J=E.reviewed_at?`${this._t("lbl.reviewed_on",{date:new Date(E.reviewed_at).toLocaleDateString()},`reviewed ${new Date(E.reviewed_at).toLocaleDateString()}`)}`:"",G=it(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.")),X=it(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.")),ct=it(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=it(this._t("msg.review_tags_tip",{},"Optional flags describing what went wrong with this cycle, so training and cleanup can account for it.")),at=it(this._t("msg.review_notes_tip",{},"Free-text notes for your own reference. Not used by matching or training."));R=` ${C}

${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.")}

${J}
${this._t("lbl.compare_profiles",{},"Compare with profiles")}${it(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(f=>{const m=(t.overlays||[]).includes(f.name),x=m?``:"";return``}).join("")||`${this._t("msg.no_profiles_compare",{},"No profiles to compare.")}`}
${this._t("lbl.tags",{},"Tags")}${U}
${Q}
${this._t("lbl.notes",{},"Notes")}${at}
`}let B="";const W=t.mode==="view"||t.mode==="review"?e.artifacts||[]:[];if(W.length){const E=W.map(M=>{const H=M.detail_key?this._t(M.detail_key,M.detail_params||{},M.detail||""):M.detail||"";return`
  • ${d(Zt(M.type,(q,Q,J)=>this._t(q,Q,J)))} ${this._t("lbl.at",{},"at")} ${Mt(M.start_s)}\u2013${Mt(M.end_s)} \u2014 ${d(H)}
  • `}).join("");B=`
    \u26A0 ${this._t("msg.artifact_header",{n:W.length},`${W.length} anomal${W.length>1?"ies":"y"} detected during this cycle`)}
      ${E}
    ${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 O="";const D=t.mode==="view"||t.mode==="review"?e.restart_gaps||[]:[];if(D.length){const E=D.map(M=>{const H=Math.round((M.gap_seconds||0)/60),q=H>=1?`${H}m`:`${Math.round(M.gap_seconds||0)}s`,Q=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`)}`:"",J=M.profile?` (${d(M.profile)})`:"";return`
  • ${this._t("msg.restart_gap_item",{dur:q},`${q} gap`)}: ${this._t("lbl.ha_restarted",{},"HA restarted")}${J}${Q}
  • `}).join("");O=`
    \u21BB ${this._t("msg.restart_gap_header",{n:D.length},`${D.length} HA restart gap${D.length>1?"s":""} during this cycle`)}
      ${E}
    ${this._t("msg.restart_gap_footer",{},"Highlighted on the graph. Power data is missing for these intervals \u2014 matching used only real readings.")}
    `}let A="";if(e.decimated){const E=(e.samples||[]).length,M=e.sample_count||E;A=`
    ${this._t("msg.samples_decimated",{shown:E,total:M},`Showing ${E} of ${M} samples (thinned for display; peaks kept). A wide gap here is thinning, not missing data.`)}
    `}return`

    ${this._t("lbl.cycle",{},"Cycle")} \xB7 ${d(st(e.start_time))}

    ${_}${$}
    ${A} ${B} ${O} ${R}`}_htmlProfilePanel(t){const e=this._canEdit();t.tab==="danger"&&!e&&(t.tab="stats");const s=[["stats",this._t("tab.pp_overview",{},"Overview")],["phases",this._t("tab.pp_phases",{},"Phases")],["cleanup",this._t("tab.pp_cleanup",{},"Cleanup")]];e&&s.push(["danger",this._t("tab.pp_manage",{},"Manage")]);const r=s.map(([o,i])=>``).join("");let n="";if(!t.loaded)n=`
    \u23F3
    ${this._t("msg.loading",{},"Loading\u2026")}
    `;else if(t.tab==="stats"){const o=t.stats||{},i=t.env||{},l=this._hass&&this._hass.config&&this._hass.config.currency||"",c=o.avg_energy!=null&&o.cycle_count?o.avg_energy*o.cycle_count:null,_=b=>b?Math.round(b/60)+"m":"-",p=(this._profileHealth||{})[t.name],g=(this._profileTrends||{})[t.name],u=p&&p.health_status!=="unknown"?(()=>{const b={healthy:["var(--success-color,#4caf50)","rgba(76,175,80,.12)"],fair:["var(--warning-color,#ff9800)","rgba(255,152,0,.12)"],poor:["var(--error-color,#f44336)","rgba(244,67,54,.12)"]},[$,k]=b[p.health_status]||b.fair,S=Math.round((p.health_score||0)*100),C=p.duration_cv!=null?` \xB7 ${this._t("stat.duration_cv",{pct:Math.round(p.duration_cv*100)},`duration CV ${Math.round(p.duration_cv*100)}%`)}`:"",R=p.confidence_mean!=null?` \xB7 ${this._t("stat.avg_confidence",{pct:Math.round(p.confidence_mean*100)},`avg confidence ${Math.round(p.confidence_mean*100)}%`)}`:"";return`
    ${p.health_status==="poor"?this._t("health.poor",{},"\u26A0 Poor match fit"):p.health_status==="fair"?this._t("health.fair",{},"Fair match fit"):this._t("health.good",{},"\u2713 Good match fit")} ${this._t("stat.score",{pct:S},`score ${S}%`)}${C}${R} ${p.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.")}`:""}
    `})():"",v=g&&(g.duration_trend!=="stable"||g.energy_trend&&g.energy_trend!=="stable")?(()=>{const b=[];g.duration_trend==="up"?b.push(this._t("msg.trend_duration_longer",{pct:`${g.duration_slope_pct>0?"+":""}${g.duration_slope_pct}`,avg:`${Math.round(g.duration_recent_mean_s/60)}m`},`Duration trending longer (${g.duration_slope_pct>0?"+":""}${g.duration_slope_pct}%/cycle) \u2014 recent avg ${Math.round(g.duration_recent_mean_s/60)}m`)):g.duration_trend==="down"&&b.push(this._t("msg.trend_duration_shorter",{pct:`${g.duration_slope_pct}`,avg:`${Math.round(g.duration_recent_mean_s/60)}m`},`Duration trending shorter (${g.duration_slope_pct}%/cycle) \u2014 recent avg ${Math.round(g.duration_recent_mean_s/60)}m`)),g.energy_trend==="up"?b.push(this._t("msg.trend_energy_up",{pct:`${g.energy_slope_pct>0?"+":""}${g.energy_slope_pct}`,avg:gt(g.energy_recent_mean_wh)},`Energy trending up (${g.energy_slope_pct>0?"+":""}${g.energy_slope_pct}%/cycle) \u2014 recent avg ${gt(g.energy_recent_mean_wh)}`)):g.energy_trend==="down"&&b.push(this._t("msg.trend_energy_down",{pct:`${g.energy_slope_pct}`},`Energy trending down (${g.energy_slope_pct}%/cycle)`));const $=g.duration_trend==="up"||g.energy_trend==="up";return`
    ${this._t("msg.performance_trend",{n:g.cycle_count},`Performance trend (${g.cycle_count} cycles)`)}
    ${b.map(C=>`${C}`).join("
    ")} ${$?`
    ${this._t("msg.maintenance_advisory",{},"Increasing duration/energy may indicate appliance maintenance needed (e.g. descaling, filter cleaning).")}`:""}
    `})():"";n=`
    ${this._t("lbl.duration",{},"Duration")}
    ${_(o.avg_duration)}${this._t("stat.avg",{},"avg")}
    ${this._t("stat.min",{v:_(o.min_duration)},`min ${_(o.min_duration)}`)} \xB7 ${this._t("stat.max",{v:_(o.max_duration)},`max ${_(o.max_duration)}`)}${i.duration_std_dev!=null?` \xB7 ${this._t("stat.consistency",{v:`${Math.round(i.duration_std_dev/60)}m`},`consistency \xB1${Math.round(i.duration_std_dev/60)}m`)}`:""}
    ${this._t("lbl.energy",{},"Energy")}
    ${gt(o.avg_energy)}${this._t("stat.avg",{},"avg")}
    ${this._t("stat.total",{v:gt(c)},`total ${gt(c)}`)}
    ${o.avg_cost!=null?`
    ${this._t("lbl.avg_cost",{},"Avg cost")}
    ${o.avg_cost.toFixed(2)}${l?" "+l:""}${this._t("stat.avg",{},"avg")}
    ${this._t("stat.total",{v:o.total_cost!=null?o.total_cost.toFixed(2)+(l?" "+l:""):"-"},`total ${o.total_cost!=null?o.total_cost.toFixed(2)+(l?" "+l:""):"-"}`)}
    `:""}
    ${this._t("lbl.activity",{},"Activity")}
    ${o.cycle_count||0}${this._t("lbl.cycles_lc",{},"cycles")}
    ${this._t("stat.last_run",{v:o.last_run?st(o.last_run):"-"},`last run ${o.last_run?st(o.last_run):"-"}`)}
    ${u} ${v} ${p&&p.shape_drift?(()=>{const b=p.shape_drift_correlation!=null?` (r=${Number(p.shape_drift_correlation).toFixed(2)})`:"";return`
    ${this._t("msg.shape_drift_advisory",{},"\u26A0 Shape drifting")}${d(b)} ${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).")}
    `})():""} ${(()=>{const b=(this._profileAdvisories||[]).find($=>$&&$.profile===t.name&&$.code==="phase_inconsistent");return b?`
    ${this._t("msg.advisory_phase_inconsistent_title",{},"\u26A0 Possibly mixed programs")} ${d(this._t(b.message_key,b.message_params,b.message))}
    `:""})()} ${i.avg&&i.avg.length?`
    `:`

    ${this._t("msg.no_envelope",{},"No envelope yet - rebuild after labelling cycles.")}

    `}`}else if(t.tab==="phases"){const o=t.catalog||[],i=(t.phases||[]).map((c,_)=>{const p=o.map(g=>``).join("");return`
    \u2013 ${this._t("lbl.timer_min",{},"min")}
    `}).join(""),l=this._busy.has("pp-phase-save");n=`

    ${this._t("msg.phase_ranges_intro",{},"Phase ranges (minutes from cycle start) overlaid on the average curve. Edit values to preview live.")}

    ${t.env&&t.env.avg&&t.env.avg.length?`
    `:`

    ${this._t("msg.no_envelope_overlay",{},"No envelope available to overlay.")}

    `}
    ${i||`

    ${this._t("msg.no_phases_assigned",{},"No phases assigned.")}

    `}
    ${e?`
    `:""}`}else if(t.tab==="cleanup"){const o=t.cleanup&&t.cleanup.cycles||[],i=t.cleanup&&t.cleanup.selected||new Set,{col:l,dir:c}=this._cleanupSort,_={date:b=>b.start_time?new Date(b.start_time).getTime():0,duration:b=>b.duration,energy:b=>b.energy_kwh,status:b=>b.status||""},g=Qt(o,_[l]||_.date,c).map((b,$)=>{const k=o.indexOf(b),S=e?` `:"";return` ${st(b.start_time)} ${ot(b.duration)} ${b.energy_kwh!=null?gt(b.energy_kwh):"-"} ${d(b.status||"completed")} ${S} `}).join(""),u=` ${ft(this._t("lbl.date",{},"Date"),"date",l==="date",c,"cleanupsort")} ${ft(this._t("lbl.duration",{},"Duration"),"duration",l==="duration",c,"cleanupsort","right")} ${ft(this._t("lbl.energy",{},"Energy"),"energy",l==="energy",c,"cleanupsort","right")} ${ft(this._t("lbl.status",{},"Status"),"status",l==="status",c,"cleanupsort")} ${e?"":""} `,v=this._busy.has("pp-cleanup-del");n=`

    ${this._t("msg.cleanup_intro",{},"Every labelled cycle overlaid. Tick outliers and delete to clean up the profile.")}

    ${o.length?`
    `:`

    ${this._t("msg.no_cycles_profile",{},"No cycles for this profile.")}

    `} ${o.length?`
    ${u}${g}
    `:""} ${e?`
    `:""}`}else if(t.tab==="danger"){const o=this._busy.has("pp-rebuild"),i=t.stats&&t.stats.avg_duration?Math.round(t.stats.avg_duration/60):0;n=`
    ${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.")}
    `}const a=this._onlineEnabled()&&this._storeDeviceDeclared()?``:"";return`

    Profile \xB7 ${d(t.name)}

    ${r}
    ${n}
    ${a}
    `}_drawCycleEditor(){const t=this._modal;if(!t||t.type!=="cycle-detail"||!t.loaded)return;const e=t.curve||{},s=e.samples||[];if(!s.length)return;let r=e.full_duration_s||s[s.length-1][0]||1;const n=[],a=t.profileEnv;if((t.mode==="view"||t.mode==="review")&&a&&(a.avg||[]).length&&(n.push({points:a.avg,stroke:"#ff9800",width:2,alpha:.45,name:`${this._t("lbl.expected",{},"Expected")} (${e.profile_name||"profile"})`}),r=Math.max(r,a.target_duration||a.avg[a.avg.length-1][0]||0)),t.mode==="review"&&(t.overlays||[]).length){const c=this._profileEnvCache||{};(t.overlays||[]).forEach(_=>{const p=c[_];if(!p||!(p.avg||[]).length)return;const g=et[Math.max(0,(this._profiles||[]).findIndex(v=>v.name===_))%et.length];n.push({points:p.avg,stroke:g,width:1.6,alpha:.7,name:_});const u=p.avg[p.avg.length-1];r=Math.max(r,p.target_duration||(u?u[0]:0))})}n.push({points:s,stroke:"primary",fill:!0,width:2,name:this._t("lbl.power",{},"Power")});const o=[],i=[];let l=[];if(t.mode==="trim"){const c=t.trim.start,_=t.trim.end;o.push({x0:0,x1:c,fill:"rgba(244,67,54,.18)"}),o.push({x0:_,x1:r,fill:"rgba(244,67,54,.18)"}),i.push({x:c,color:"#f44336",label:"S"},{x:_,color:"#f44336",label:"E"})}else if(t.mode==="split")(t.split.offsets||[]).slice().sort((c,_)=>c-_).forEach((c,_)=>i.push({x:c,color:"#ff9800",label:"#"+(_+1)}));else{l=e.artifacts||[];const c={pause:"rgba(255,152,0,.22)",dip:"rgba(33,150,243,.18)",spike:"rgba(244,67,54,.18)"};l.forEach(p=>o.push({x0:p.start_s,x1:Math.max(p.end_s,p.start_s+1),fill:c[p.type]||"rgba(158,158,158,.18)"}));const _=e.start_time;(e.restart_gaps||[]).forEach(p=>{if(!_)return;const g=new Date(_).getTime(),u=Math.max(0,(new Date(p.start_ts).getTime()-g)/1e3),v=Math.max(u+1,(new Date(p.end_ts).getTime()-g)/1e3);o.push({x0:u,x1:v,fill:"rgba(96,125,139,.20)",label:"\u21BB"})})}this._drawCurves("wd-cyc-canvas",{series:n,xMax:r,bands:o,vlines:i,artifacts:l})}_htmlCompareModal(t){const e=t.ids||[],s={};(this._cycles||[]).forEach(o=>{s[o.id]=o});const r=t.hidden||new Set,n=e.map((o,i)=>{const l=s[o]||{},c=et[i%et.length],_=!r.has(o),p=!!(t.cycles&&t.cycles[o]),g=`${st(l.start_time)||String(o).slice(0,8)} \xB7 ${Math.round((l.duration||0)/60)}m \xB7 ${d(l.profile_name||this._t("lbl.unlabelled",{},"Unlabelled"))}`;return``}).join(""),a=(this._profiles||[]).map(o=>{const i=(t.overlays||[]).includes(o.name),l=et[Math.max(0,(this._profiles||[]).findIndex(_=>_.name===o.name))%et.length],c=i?``:"";return``}).join("")||`${this._t("msg.no_profiles_overlay",{},"No profiles to overlay.")}`;return`

    ${this._t("msg.compare_cycles_title",{count:e.length},`Compare ${e.length} cycles`)}

    ${t.loaded?"":`
    ${this._t("msg.loading",{},"Loading\u2026")}
    `}
    ${this._t("msg.compare_selected_cycles",{},"Selected cycles (solid) \u2014 show / hide")}
    ${n}
    ${this._t("msg.compare_overlay_profiles",{},"Overlay profiles (faint)")}${it(this._t("msg.compare_overlay_tip",{},"Overlay learned profile envelopes to see which program each cycle resembles."))}
    ${a}
    `}_drawCompareCanvas(){const t=this._modal;if(!t||t.type!=="compare-cycles")return;const e=t.ids||[],s={};(this._cycles||[]).forEach(i=>{s[i.id]=i});const r=t.hidden||new Set,n=[];let a=0;const o=this._profileEnvCache||{};(t.overlays||[]).forEach(i=>{const l=o[i];if(!l||!(l.avg||[]).length)return;const c=et[Math.max(0,(this._profiles||[]).findIndex(p=>p.name===i))%et.length];n.push({points:l.avg,stroke:c,width:2,alpha:.4,name:i});const _=l.avg[l.avg.length-1];a=Math.max(a,l.target_duration||(_?_[0]:0))}),e.forEach((i,l)=>{if(r.has(i))return;const c=t.cycles&&t.cycles[i],_=c&&c.samples;if(!_||!_.length)return;const p=et[l%et.length],g=s[i]||{};n.push({points:_,stroke:p,width:1.8,alpha:.9,name:st(g.start_time)||String(i).slice(0,8)}),a=Math.max(a,c.full_duration_s||_[_.length-1][0]||0)}),this._drawCurves("wd-compare-canvas",{series:n,xMax:a||1})}_drawProfileEnvelope(){const t=this._modal;if(!t||!t.env||!(t.env.avg||[]).length)return;const e=t.env;this._drawCurves("wd-env-canvas",{series:[{points:e.avg,stroke:"primary",width:2,name:"Average"}],band:{min:e.min,max:e.max},xMax:e.target_duration||e.avg[e.avg.length-1][0]})}_drawPhaseEditor(){const t=this._modal;if(!t||!t.env||!(t.env.avg||[]).length)return;const e=t.env,s=e.target_duration||e.avg[e.avg.length-1][0],r=(t.phases||[]).map((a,o)=>({x0:a.start,x1:a.end,fill:St(et[o%et.length],.2)})),n=[];(t.phases||[]).forEach((a,o)=>{const i=et[o%et.length];n.push({x:a.start,color:i,label:a.name?a.name.slice(0,7):"",handle:!0}),n.push({x:a.end,color:i,handle:!0})}),this._drawCurves("wd-phase-canvas",{series:[{points:e.avg,stroke:"primary",width:2,name:"Average"}],band:{min:e.min,max:e.max},bands:r,vlines:n,xMax:s})}_drawSpaghetti(){const t=this._modal;if(!t||!t.cleanup||!(t.cleanup.cycles||[]).length)return;const e=t.cleanup.cycles,s=t.cleanup.selected||new Set,r=this._spagTableHoverCid||null;let n=1;e.forEach(o=>{const i=o.samples||[];i.length&&i[i.length-1][0]>n&&(n=i[i.length-1][0])});const a=e.map((o,i)=>{const l=s.has(o.cycle_id),c=r===o.cycle_id;return{points:o.samples||[],stroke:et[i%et.length],width:l||c?2.6:1,alpha:s.size?l?1:.22:r?c?1:.22:.7,name:st(o.start_time),cid:o.cycle_id}});this._drawCurves("wd-spag-canvas",{series:a,xMax:n})}_wire(){const t=this.shadowRoot;if(!t)return;const e=t.getElementById("wd-burger");e&&e.addEventListener("click",()=>{this.dispatchEvent(new CustomEvent("hass-toggle-menu",{bubbles:!0,composed:!0}))}),t.querySelectorAll(".wd-devcard[data-idx]").forEach(f=>f.addEventListener("click",()=>this._selectDevice(parseInt(f.dataset.idx,10)))),t.querySelectorAll("[data-tab]").forEach(f=>f.addEventListener("click",()=>{f.dataset.tab!=="settings"&&(this._pendingSettings={}),this._tab=f.dataset.tab,this._fetchTabData()})),t.querySelectorAll("[data-sec]").forEach(f=>f.addEventListener("click",()=>{this._snapshotFormToPending(t),this._settingsSec=f.dataset.sec,this._settingsSearch="",this._settingsSugOnly=!1,this._render()})),t.querySelectorAll("[data-ptab]").forEach(f=>f.addEventListener("click",()=>{const m=this._panelSubtab=f.dataset.ptab;this._render();const x=this._devices[this._selIdx];x&&(m==="diagnostics"&&!this._diag?this._fetchToolsData(x.entry_id).then(()=>{this._panelSubtab==="diagnostics"&&this._render()}):m==="logs"?this._fetchLogs().then(()=>{this._panelSubtab==="logs"&&this._render()}):m==="maintenance"?this._fetchMaintenance(x.entry_id).then(()=>{this._panelSubtab==="maintenance"&&this._render()}):m==="ml"&&this._fetchTabData())})),t.querySelectorAll("[data-gtab]").forEach(f=>f.addEventListener("click",()=>{this._gearTab=f.dataset.gtab,this._modal&&this._modal.type==="gear-settings"&&(this._modal.tab=this._gearTab);const m=this._devices[this._selIdx];this._gearTab==="online"&&m&&this._onlineEnabled()&&(this._ensureStoreConnectListener(),this._loadStoreStatus(m.entry_id).then(()=>{this._modal&&this._modal.type==="gear-settings"&&this._render()})),this._render()}));const s=t.getElementById("wd-store-brand");s&&s.addEventListener("change",()=>{const f=s.value.trim();this._opts={...this._opts,store_brand:f},delete this._pendingSettings.store_brand,this._catalog.forBrand=f,this._catalog.devices=void 0,this._render()});const r=t.getElementById("wd-store-model");r&&r.addEventListener("change",()=>{this._opts={...this._opts,store_model:r.value.trim()},delete this._pendingSettings.store_model,this._render()});const n=t.getElementById("wd-pg-canvas");if(n){const f=()=>{const I=n.getBoundingClientRect(),w=I.height||330,P=Bt+8,z=w-P-52;return{rect:I,padT:P,powerH:z}},m=(I,w)=>{const P=n.getBoundingClientRect(),z=I-P.left,F=w-P.top;if(F>Bt+4)return null;const K=this._pgEventHits||[];let Z=null,rt=1/0;for(const ht of K){const nt=(z-ht.cx)**2+(F-ht.cy)**2;nt<=(ht.r+4)**2&&nt{const{rect:w,padT:P,powerH:z}=f(),F=this._pgPowerPts;if(!F?.length)return 0;const K=Math.max(...F.map(Z=>Z.w),1);return Math.max(0,(1-Math.max(0,I-w.top-P)/z)*K)},L=I=>{const{rect:w,padT:P,powerH:z}=f(),F=this._pgPowerPts;if(!F?.length)return 0;const K=Math.max(...F.map(Z=>Z.w),1);return w.top+P+(1-Math.max(0,+I)/K)*z},j=I=>{const w=this._pgMap;if(!w)return null;const{rect:P}=f(),z=(I-P.left-w.padLpx)/Math.max(1,w.plotWpx);return w.vMin+Math.max(0,Math.min(1,z))*(w.vMax-w.vMin)};n.addEventListener("pointermove",I=>{if(this._pgDragging==="start_thr"){this._pgThreshStart=Math.max(0,x(I.clientY)),this._pgUpdateParamInput("start_threshold_w",this._pgThreshStart),this._pgDrawCanvas();return}if(this._pgDragging==="stop_thr"){this._pgThreshStop=Math.max(0,x(I.clientY)),this._pgUpdateParamInput("stop_threshold_w",this._pgThreshStop),this._pgDrawCanvas();return}if(this._pgDragging==="pan"&&this._pgPanStart){const Z=this._pgMap,rt=(I.clientX-this._pgPanStart.clientX)/Math.max(1,Z.plotWpx),ht=this._pgPanStart.vMax-this._pgPanStart.vMin;let nt=this._pgPanStart.vMin-rt*ht;nt=Math.max(0,Math.min(this._pgPanStart.totalDur-ht,nt)),this._pgView={min:nt,max:nt+ht},this._pgDrawCanvas();return}if(!this._pgPowerPts?.length)return;const w=m(I.clientX,I.clientY);if(w){n.style.cursor="pointer",this._pgHoverEvent={t:w.t,type:w.type},this._pgHoverT=null,this._pgDrawCanvas();return}this._pgHoverEvent&&(this._pgHoverEvent=null,this._pgDrawCanvas());const P=this._pgThreshStart??this._pgFieldVal("start_threshold_w",{})??50,z=this._pgThreshStop??this._pgFieldVal("stop_threshold_w",{})??5,F=Math.abs(I.clientY-L(P))<8||Math.abs(I.clientY-L(z))<8;n.style.cursor=F?"ns-resize":"crosshair";const K=j(I.clientX);K!=null&&(this._pgHoverT=K,this._pgUpdateStripAt(K),this._pgDrawCanvas())}),n.addEventListener("pointerdown",I=>{if(!this._pgPowerPts?.length||m(I.clientX,I.clientY))return;n.setPointerCapture(I.pointerId);const w=this._pgThreshStart??this._pgFieldVal("start_threshold_w",{})??50,P=this._pgThreshStop??this._pgFieldVal("stop_threshold_w",{})??5;Math.abs(I.clientY-L(w))<10?this._pgDragging="start_thr":Math.abs(I.clientY-L(P))<10?this._pgDragging="stop_thr":this._pgMap&&(this._pgDragging="pan",this._pgPanStart={clientX:I.clientX,vMin:this._pgMap.vMin,vMax:this._pgMap.vMax,totalDur:this._pgMap.totalDur},n.classList.add("wd-pg-panning"))}),n.addEventListener("pointerup",I=>{try{n.releasePointerCapture(I.pointerId)}catch{}const w=this._pgDragging==="start_thr"||this._pgDragging==="stop_thr";this._pgDragging=null,this._pgPanStart=null,n.classList.remove("wd-pg-panning"),w&&this._pgDrawCanvas()}),n.addEventListener("pointerleave",()=>{this._pgDragging||(this._pgHoverT=null,this._pgHoverEvent=null,this._pgUpdateStripAt(null),this._pgDrawCanvas())}),n.addEventListener("wheel",I=>{if(!this._pgPowerPts?.length||!this._pgMap)return;I.preventDefault();const w=this._pgMap,P=j(I.clientX);if(P==null)return;const z=w.vMax-w.vMin,F=I.deltaY>0?1.25:.8,K=Math.max(30,Math.min(w.totalDur,z*F)),Z=z>0?(P-w.vMin)/z:.5;let rt=P-Z*K;rt=Math.max(0,Math.min(w.totalDur-K,rt)),this._pgView=K>=w.totalDur-1?null:{min:rt,max:rt+K},this._pgDrawCanvas()},{passive:!1}),n.addEventListener("dblclick",()=>{this._pgView=null,this._pgDrawCanvas()})}t.querySelectorAll("input[data-pgkey]").forEach(f=>{f.addEventListener("input",()=>{const m=f.dataset.pgkey;if(f.dataset.pgtype==="bool"){const w=f.checked;m==="start_threshold_w"?this._pgThreshStart=w:m==="stop_threshold_w"?this._pgThreshStop=w:this._pgParamOverrides[m]=w,this._render();const P=t.querySelector(`input[data-pgkey="${m}"]`);P&&P.focus(),requestAnimationFrame(()=>this._pgDrawCanvas());return}const x=f.value.trim();if(!x)m==="start_threshold_w"?this._pgThreshStart=null:m==="stop_threshold_w"?this._pgThreshStop=null:delete this._pgParamOverrides[m];else{const w=parseFloat(x);isNaN(w)||(m==="start_threshold_w"?this._pgThreshStart=w:m==="stop_threshold_w"?this._pgThreshStop=w:this._pgParamOverrides[m]=w)}const L=f.selectionStart,j=f.value;this._render();const I=t.querySelector(`input[data-pgkey="${m}"]`);if(I){I.value=j,I.focus();try{I.setSelectionRange(L,L)}catch{}}requestAnimationFrame(()=>this._pgDrawCanvas())})});const a=t.getElementById("wd-pg-stress-toggle");a&&a.addEventListener("change",()=>{this._pgStressTail=a.checked,this._pgStressIdleW=null,this._render()});const o=t.getElementById("wd-pg-stress-idle-w");o&&o.addEventListener("input",()=>{const f=parseFloat(o.value);this._pgStressIdleW=Number.isFinite(f)&&f>=0?f:null});const i=t.getElementById("wd-pg-cyc-sel");i&&i.addEventListener("change",()=>this._pgSelectCycle(i.value));const l=t.getElementById("wd-pg-prof-sel");l&&l.addEventListener("change",()=>{this._pgProfileName=l.value,this._pgLoad()});const c=t.getElementById("wd-pg-preset-sel");c&&c.addEventListener("change",()=>{this._pgPresetSel=c.value,this._render()});const _=t.getElementById("wd-pg-preset-name");_&&_.addEventListener("input",()=>{this._pgPresetName=_.value;const f=(this._pgPresetName||"").trim(),m=this._pgPresetLimit>0&&(this._pgPresets||[]).length>=this._pgPresetLimit&&!(this._pgPresets||[]).some(j=>j.name===f),x=t.querySelector('[data-action="pg-preset-save"]');x&&(x.disabled=!(f&&!m));const L=t.getElementById("wd-pg-preset-limit-note");L&&(L.style.display=m?"":"none")});const p=t.getElementById("wd-pg-simn");p&&p.addEventListener("input",()=>{this._pgSimCycles=Math.max(1,Math.min(200,parseInt(p.value,10)||20))});const g=t.getElementById("wd-pg-sw-param");g&&g.addEventListener("change",()=>{this._pgSweepParam=g.value,this._pgSweepNew=null,this._render()});const u=t.getElementById("wd-pg-sw-obj");u&&u.addEventListener("change",()=>{this._pgSweepObjective=u.value,this._pgSweepNew=null,this._render()});const v=t.getElementById("wd-pg-sw-paramy"),b=t.getElementById("wd-pg-sw-from");b&&b.addEventListener("input",()=>{this._pgSweepFrom=b.value});const $=t.getElementById("wd-pg-sw-to");$&&$.addEventListener("input",()=>{this._pgSweepTo=$.value});const k=t.getElementById("wd-pg-sw-steps");k&&k.addEventListener("input",()=>{this._pgSweepSteps=parseInt(k.value,10)||5}),t.querySelectorAll("[data-statustoggle]").forEach(f=>f.addEventListener("change",async()=>{const m=f.dataset.statustoggle,x=f.checked;this._panelCfg||(this._panelCfg={}),this._panelCfg.prefs={...this._panelCfg.prefs||{},[m]:x},this._ws({type:`${y}/set_user_prefs`,prefs:{[m]:x}}).catch(()=>{});const L=this._devices[this._selIdx];if(L&&this._tab==="status")try{this._powerData=await this._ws({type:`${y}/get_power_history`,entry_id:L.entry_id,with_raw:this._pref("show_raw_active",!1)})}catch{}this._render()})),t.querySelectorAll("[data-sortact]").forEach(f=>f.addEventListener("click",()=>{const m=f.dataset.sortact,x=f.dataset.sortcol,L=j=>{j.col===x?j.dir*=-1:(j.col=x,j.dir=x==="date"?-1:1)};m==="cycsort"?L(this._cycleSort):m==="cleanupsort"&&L(this._cleanupSort),this._render()}));const S=t.getElementById("wd-cyc-filter-text");S&&S.addEventListener("input",f=>{const m=f.target.selectionStart;this._cycleFilter.text=S.value,this._render();const x=this.shadowRoot.getElementById("wd-cyc-filter-text");x&&(x.focus(),x.setSelectionRange(m,m))});const C=t.getElementById("wd-settings-search");C&&C.addEventListener("input",f=>{const m=f.target.selectionStart;this._settingsSearch=C.value,C.value.trim()&&(this._settingsSugOnly=!1),this._render();const x=this.shadowRoot.getElementById("wd-settings-search");x&&(x.focus(),x.setSelectionRange(m,m))});const R=t.getElementById("wd-cyc-filter-status");R&&R.addEventListener("change",()=>{this._cycleFilter.status=R.value,this._render()}),t.querySelectorAll(".wd-pillbox").forEach(f=>{const m=f.querySelector(".wd-pill-add"),x=j=>{const I=document.createElement("span");I.className="wd-pill",I.dataset.val=j,I.appendChild(document.createTextNode(j));const w=document.createElement("button");return w.type="button",w.className="wd-pill-x",w.setAttribute("aria-label","Remove"),w.textContent="\xD7",w.addEventListener("click",()=>I.remove()),I.appendChild(w),I},L=j=>{const I=String(j||"").trim();if(!I)return;Array.from(f.querySelectorAll(".wd-pill")).some(P=>P.dataset.val===I)||f.insertBefore(x(I),m.closest(".wd-combo")||m),m&&(m.value="")};f.querySelectorAll(".wd-pill-x").forEach(j=>j.addEventListener("click",()=>j.closest(".wd-pill")?.remove())),m&&(m.addEventListener("change",()=>L(m.value)),m.addEventListener("keydown",j=>{j.key==="Enter"&&(j.preventDefault(),L(m.value))}),m.addEventListener("blur",()=>L(m.value)))}),t.querySelectorAll(".wd-combo").forEach(f=>{const m=f.querySelector(".wd-combo-inp, .wd-pill-add"),x=f.querySelector(".wd-combo-drop");if(!m||!x)return;const L=f.classList.contains("wd-combo-pill"),j=m.dataset.opt||f.closest("[data-opt]")?.dataset.opt,I=P=>{this._ensureCatalogList(j,P);const z=(this._entityListCache||{})[j]||[],F=(P||"").toLowerCase(),K=F?z.filter(Z=>Z.toLowerCase().includes(F)).slice(0,40):z.slice(0,20);if(!K.length){x.hidden=!0;return}x.innerHTML=K.map(Z=>`
    ${d(Z)}
    `).join(""),x._kbd=-1,x.hidden=!1},w=P=>{if(P){if(L){const z=f.closest(".wd-pillbox");if(z&&!Array.from(z.querySelectorAll(".wd-pill")).some(F=>F.dataset.val===P)){const F=document.createElement("span");F.className="wd-pill",F.dataset.val=P,F.appendChild(document.createTextNode(P));const K=document.createElement("button");K.type="button",K.className="wd-pill-x",K.setAttribute("aria-label","Remove"),K.textContent="\xD7",K.addEventListener("click",()=>F.remove()),F.appendChild(K),z.insertBefore(F,f)}m.value=""}else m.value=P,m.dispatchEvent(new Event("change",{bubbles:!0}));x.hidden=!0}};m.addEventListener("focus",()=>I(m.value)),m.addEventListener("input",()=>I(m.value)),m.addEventListener("blur",()=>setTimeout(()=>{x.hidden=!0},150)),m.addEventListener("keydown",P=>{if(x.hidden&&P.key!=="ArrowDown")return;const z=x.querySelectorAll(".wd-combo-item");let F=x._kbd||-1;if(P.key==="ArrowDown"){if(P.preventDefault(),x.hidden){I(m.value);return}F=Math.min(F+1,z.length-1)}else if(P.key==="ArrowUp")P.preventDefault(),F=Math.max(F-1,0);else if(P.key==="Enter"&&!x.hidden){P.preventDefault(),F>=0?w(z[F].dataset.val):L&&m.value.trim()&&w(m.value.trim());return}else if(P.key==="Escape"){x.hidden=!0;return}else return;x._kbd=F,z.forEach((K,Z)=>K.classList.toggle("kbd",Z===F)),z[F]?.scrollIntoView({block:"nearest"})}),x.addEventListener("mousedown",P=>{const z=P.target.closest(".wd-combo-item");z&&(P.preventDefault(),w(z.dataset.val))})}),t.querySelectorAll(".wd-timerlist").forEach(f=>{const m=f.dataset.opt,x=()=>{if(!Array.isArray(this._pendingSettings[m])){const z=Array.isArray(this._opts&&this._opts[m])?this._opts[m]:[];this._pendingSettings[m]=z.map(F=>({...F}))}return this._pendingSettings[m]},L=z=>({offset_minutes:parseFloat(z.querySelector('[data-field="offset_minutes"]').value)||0,message:(z.querySelector('[data-field="message"]').value||"").trim(),auto_pause:z.querySelector('[data-field="auto_pause"]').checked}),j=z=>{const F=parseInt(z.dataset.tidx,10);if(isNaN(F))return;const K=x();K[F]=L(z)},I=z=>{const F=parseInt(z.dataset.tidx,10);isNaN(F)||(this._pendingSettings[m]=x().filter((K,Z)=>Z!==F)),z.remove(),f.querySelectorAll(".wd-timer-row").forEach((K,Z)=>{K.dataset.tidx=Z})},w=z=>{const F=z.querySelector(".wd-timer-remove");F&&F.addEventListener("click",()=>I(z)),z.querySelector('[data-field="auto_pause"]')?.addEventListener("change",()=>j(z)),z.querySelectorAll('[data-field="offset_minutes"],[data-field="message"]').forEach(K=>K.addEventListener("input",()=>j(z)))};f.querySelectorAll(".wd-timer-row").forEach(z=>w(z));const P=f.querySelector(".wd-timer-add");P&&P.addEventListener("click",()=>{const z=f.querySelectorAll(".wd-timer-row").length;x().push({offset_minutes:0,message:"",auto_pause:!1});const F=document.createElement("div");F.className="wd-timer-row",F.dataset.tidx=z,F.innerHTML=`
    `,w(F),f.insertBefore(F,P)})});const B=t.getElementById("wd-status-prog");B&&B.addEventListener("change",()=>{const f=this._devices[this._selIdx];if(!f)return;const m=B.value;this._ws({type:`${y}/set_program`,entry_id:f.entry_id,program:m}).then(()=>(this._showToast(m==="auto_detect"?this._t("msg.toast_auto_detect_enabled",{},"Auto-detect enabled"):this._t("msg.toast_program_set",{program:m},`Program set: ${m}`)),this._fetchAll())).catch(x=>this._showToast(this._t("msg.toast_failed",{error:x.message||x},"Failed: "+(x.message||x)),"error"))}),t.querySelectorAll("[data-cid]").forEach(f=>f.addEventListener("click",m=>{if(m.target.tagName==="INPUT"||f.dataset.action)return;const x=f.dataset.cid;f.dataset.selmode==="1"?(this._cycleSel.has(x)?this._cycleSel.delete(x):this._cycleSel.add(x),this._render()):this._onAction({dataset:{action:"open-cycle",cid:x}})})),t.querySelectorAll(".wd-cyc-overlay").forEach(f=>f.addEventListener("change",()=>{const m=this._modal;if(!m||m.type!=="cycle-detail")return;this._snapshotCycleReviewForm(t);const x=new Set(m.overlays||[]);f.checked?x.add(f.value):x.delete(f.value),m.overlays=[...x];const L=this._devices[this._selIdx];f.checked&&L?this._ensureProfileEnvs(L.entry_id,[f.value]).then(()=>this._render()):this._render()})),t.querySelectorAll(".wd-compare-cyc").forEach(f=>f.addEventListener("change",()=>{const m=this._modal;if(!m||m.type!=="compare-cycles")return;const x=m.hidden instanceof Set?m.hidden:new Set(m.hidden||[]);f.checked?x.delete(f.value):x.add(f.value),m.hidden=x,this._render()})),t.querySelectorAll(".wd-compare-overlay").forEach(f=>f.addEventListener("change",()=>{const m=this._modal;if(!m||m.type!=="compare-cycles")return;const x=new Set(m.overlays||[]);f.checked?x.add(f.value):x.delete(f.value),m.overlays=[...x];const L=this._devices[this._selIdx];f.checked&&L?this._ensureProfileEnvs(L.entry_id,[f.value]).then(()=>this._render()):this._render()})),t.querySelectorAll(".wd-pg-mem").forEach(f=>f.addEventListener("change",()=>{const m=this._modal;if(!m||m.type!=="profile-group")return;const x=t.getElementById("wd-pg-name");x&&(m.name=x.value);const L=new Set(m.members||[]);f.checked?L.add(f.value):L.delete(f.value),m.members=[...L],this._render()}));const W=t.getElementById("wd-pg-name");W&&W.addEventListener("input",()=>{const f=this._modal;f&&f.type==="profile-group"&&(f.name=W.value)}),t.querySelectorAll(".wd-csel").forEach(f=>f.addEventListener("change",()=>{const m=f.closest("[data-cid]"),x=m&&m.dataset.cid;x&&(f.checked?this._cycleSel.add(x):this._cycleSel.delete(x),this._render())}));const O=t.getElementById("wd-merge-prof");O&&O.addEventListener("change",()=>{const f=t.getElementById("wd-merge-new");f&&(f.style.display=O.value==="__create_new__"?"":"none")}),t.querySelectorAll(".wd-log-filter").forEach(f=>{const m=f.dataset.logfilter,x={level:"_logLevel",device:"_logDevice",component:"_logComponent",search:"_logSearch"}[m];if(!x)return;const L=m==="search"?"input":"change";f.addEventListener(L,()=>{this[x]=f.value,this._refreshLogViews(),this._syncLogFilters(f)})});const D=t.querySelector(".wd-log-resize");D&&D.addEventListener("pointerdown",f=>{f.preventDefault(),D.setPointerCapture(f.pointerId),D.classList.add("dragging");const m=t.querySelector(".wd-log-drawer"),x=f.clientX,L=m.offsetWidth,j=I=>{const w=Math.max(280,Math.min(900,L+(x-I.clientX)));m.style.width=w+"px",this._logDrawerWidth=w};D.addEventListener("pointermove",j),D.addEventListener("pointerup",()=>{D.removeEventListener("pointermove",j),D.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 f=A.files&&A.files[0];if(!f)return;const m=this._modal,x=new FileReader;x.onload=()=>{const L=t.getElementById("wd-hist-csv"),j=String(x.result||"");L&&(L.value=j),m&&m.type==="history-import"&&(m.csvText=j)},x.onerror=()=>this._showToast(this._t("toast.file_read_failed",{},"Could not read that file"),"error"),x.readAsText(f)}),t.querySelectorAll("[data-hist-pick]").forEach(f=>f.addEventListener("change",()=>{const m=this._modal;if(!m||m.type!=="history-import")return;const x=parseInt(f.dataset.histPick,10);f.checked?m.accept.add(x):m.accept.delete(x),this._render(),requestAnimationFrame(()=>this._drawHistorySparklines())}));const E=t.getElementById("wd-import-file");E&&E.addEventListener("change",()=>{const f=E.files&&E.files[0];if(!f)return;const m=new FileReader;m.onload=()=>{const x=t.getElementById("wd-import-json");x&&(x.value=String(m.result||""))},m.readAsText(f)}),t.querySelectorAll("[data-stab]").forEach(f=>f.addEventListener("click",()=>{this._toolsSubtab=f.dataset.stab;const m=this._devices[this._selIdx];m&&(this._tabLoading=!0,this._render(),this._fetchToolsData(m.entry_id).then(()=>{this._tabLoading=!1,this._render()}))})),t.querySelectorAll("[data-proftab]").forEach(f=>f.addEventListener("click",async()=>{this._profSubtab=f.dataset.proftab;const m=this._devices[this._selIdx];m&&this._profSubtab==="phase-catalog"&&!this._phases.length&&(this._tabLoading=!0,this._render(),await this._fetchPhases(m.entry_id),this._tabLoading=!1),this._render()}));const M=t.getElementById("wd-settings-save");M&&M.addEventListener("click",()=>this._saveSettings());const H=t.getElementById("wd-ml-save");H&&H.addEventListener("click",()=>this._saveSettings());const q=t.getElementById("wd-settings-form");q&&(q.addEventListener("submit",f=>f.preventDefault()),q.addEventListener("input",()=>this._liveValidateSettings(t)),q.addEventListener("change",()=>this._liveValidateSettings(t)),q.addEventListener("click",f=>{const m=f.target.closest(".wd-conflict-fix");if(!m)return;const x=m.dataset.ckey,L=parseFloat(m.dataset.cval),j=q.querySelector(`[data-opt="${x}"]`);j&&!isNaN(L)&&(j.value=L,this._cascadeConflictFix(t,q,x))}),this._liveValidateSettings(t));const Q=t.getElementById("wd-settings-revert");Q&&Q.addEventListener("click",async()=>{if(!this._prevOpts)return;const f=this._devices[this._selIdx];f&&await this._busyRun("save-settings",async()=>{try{const m=this._prevOpts;await this._ws({type:`${y}/set_options`,entry_id:f.entry_id,options:m}),this._opts={...m},this._prevOpts=null,this._cascadePending={},this._preCascadeOpts=null,this._pendingSettings={},this._showToast(this._t("toast.settings_reverted",{},"Settings reverted; integration reloading")),this._render()}catch(m){this._showToast(this._t("msg.toast_revert_failed",{error:m.message||m},"Revert failed: "+(m.message||m)),"error")}})});const J=t.getElementById("wd-settings-reload");J&&J.addEventListener("click",async()=>{const f=this._devices[this._selIdx];if(f){this._prevOpts=null,this._cascadePending={},this._preCascadeOpts=null,this._pendingSettings={};const m=await this._ws({type:`${y}/get_options`,entry_id:f.entry_id});this._opts=m.options||{},this._optDefaults=m.defaults||{},await this._fetchSuggestions(f.entry_id),this._render()}});const G=t.getElementById("wd-pref-fontscale");if(G){const f=t.getElementById("wd-pref-fontscale-val");G.addEventListener("input",()=>{const m=parseFloat(G.value)||1;this._applyFontScale(m),f&&(f.textContent=Math.round(m*100)+"%")}),G.addEventListener("change",()=>{this._setPref("font_scale",parseFloat(G.value)||1)})}t.querySelectorAll("[data-action]").forEach(f=>f.addEventListener("click",m=>this._onAction(m.currentTarget))),t.querySelectorAll("[data-maction]").forEach(f=>f.addEventListener("click",m=>this._onModalAction(m.currentTarget.dataset.maction,m.currentTarget))),t.querySelectorAll("select[data-maction]").forEach(f=>f.addEventListener("change",m=>this._onModalAction(m.currentTarget.dataset.maction,m.currentTarget))),t.querySelectorAll("input[data-indeterminate]").forEach(f=>{f.indeterminate=!0});const X=t.querySelector("[data-toast-undo]");X&&X.addEventListener("click",()=>this._undoDelete(X.dataset.toastUndo)),t.querySelectorAll(".wd-create-cluster").forEach(f=>f.addEventListener("click",()=>{const m=f.dataset.name||"";this._modal={type:"create-profile",prefillName:m},this._render()})),t.querySelectorAll("[data-suglock]").forEach(f=>f.addEventListener("click",async()=>{const m=f.dataset.suglock,x=this._devices[this._selIdx],L=x&&x.entry_id;if(!(!L||!m))try{if(await this._ws({type:`${y}/set_suggestion_lock`,entry_id:L,key:m,locked:!0}),!this._isActiveEntry(L))return;this._suggestions=(this._suggestions||[]).filter(j=>j.key!==m),(this._lockedSuggestions||[]).includes(m)||(this._lockedSuggestions=this._lockedSuggestions||[]).push(m),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(f=>f.addEventListener("click",()=>{const m=f.dataset.sugkey,x=f.dataset.sugval,L=parseFloat(x);this._pendingSettings[m]=isNaN(L)?x:L,this._stagedSuggestions=!0,this._suggestions=this._suggestions.filter(w=>w.key!==m),this._showToast(this._t("msg.sug_staged",{key:m,val:x},`Set ${m} = ${x}. Save to apply.`),"info"),this._render();const j=this.shadowRoot,I=j?.getElementById("wd-settings-form");I&&this._cascadeConflictFix(j,I,m)}));const ct=t.getElementById("wd-label-profile");ct&&ct.addEventListener("change",()=>{const f=t.getElementById("wd-new-profile-row"),m=ct.value==="__create_new__";if(f&&(f.style.display=m?"":"none"),!m){const x=t.getElementById("wd-new-profile-name");x&&(x.value="")}});const U=t.getElementById("wd-cp-cycle");if(U){const f=()=>{const m=t.getElementById("wd-cp-dur");if(!m)return;const x=!!U.value;m.disabled=x,m.style.opacity=x?"0.5":""};U.addEventListener("change",f),f()}const at=t.getElementById("wd-pr-mode");at&&at.addEventListener("change",()=>{const f=t.getElementById("wd-pr-profile"),m=t.getElementById("wd-pr-existing");if(!f||!m)return;const x=at.value==="existing_profile";f.style.display=x?"none":"",m.style.display=x?"":"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=Jt(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),s.addEventListener("pointerdown",i=>{const l=s._wd;if(!l)return;const c=s.getBoundingClientRect(),_=i.clientX-c.left;e.drag=Math.abs(_-l.xToCss(e.trim.start))<=Math.abs(_-l.xToCss(e.trim.end))?"start":"end",s.setPointerCapture(i.pointerId)}),s.addEventListener("pointermove",i=>{if(!e.drag)return;const l=s._wd;if(!l)return;const c=s.getBoundingClientRect(),_=l.cssToX(i.clientX-c.left);e.drag==="start"?e.trim.start=Math.min(_,e.trim.end-1):e.trim.end=Math.max(_,e.trim.start+1),this._syncTrimInputs(),this._drawCycleEditor()});const o=()=>{e.drag&&(this._snapTrimBounds(),this._syncTrimInputs(),this._drawCycleEditor()),e.drag=null};s.addEventListener("pointerup",o),s.addEventListener("pointercancel",o)}else e.mode==="split"&&s.addEventListener("pointerdown",r=>{const n=s._wd;if(!n)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,Jt(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=>{const l=s._wd;if(!l)return null;let c=null,_=12;return(e.phases||[]).forEach((p,g)=>{[["start",p.start],["end",p.end]].forEach(([u,v])=>{const b=Math.abs(i-l.xToCss(v));b<_&&(_=b,c={idx:g,edge:u})})}),c};s.addEventListener("pointerdown",i=>{const l=s.getBoundingClientRect();e.phaseDrag=a(i.clientX-l.left),e.phaseDrag&&s.setPointerCapture(i.pointerId)}),s.addEventListener("pointermove",i=>{if(!e.phaseDrag)return;const l=s._wd;if(!l)return;const c=s.getBoundingClientRect(),_=l.cssToX(i.clientX-c.left),p=e.phases[e.phaseDrag.idx];p&&(e.phaseDrag.edge==="start"?p.start=Math.max(0,Math.min(_,p.end-n)):p.end=Math.min(r,Math.max(_,p.start+n)),this._syncPhaseInputs(e.phaseDrag.idx),this._drawPhaseEditor())});const o=()=>{e.phaseDrag=null};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,o=t.querySelector('[data-maction="pp-cleanup-del"]');o&&!this._busy.has("pp-cleanup-del")&&(o.disabled=a.size===0,o.textContent=this._t("btn.delete_selected",{n:a.size},`Delete selected (${a.size})`)),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=>{this._onGraphHoverInner(r.clientX,r.clientY,"wd-spag-canvas");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(_=>({name:_.name,start:_.start,end:_.end})),this._modal.catalog=(c.phases||[]).map(_=>_.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) 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 mt)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(_=>`${new Date(_.ts*1e3).toISOString()} ${_.level} ${_.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:zt(),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",_=s.getElementById("wd-pref-lang")?.value||"",p=parseFloat(s.getElementById("wd-pref-fontscale")?.value)||1,g={default_tab:a,show_debug:o,show_expected:i,show_raw:l,date_format:c,lang_override:_,font_scale:p};this._busyRun("save-prefs",async()=>{try{await this._ws({type:`${y}/set_user_prefs`,prefs:g}),this._panelCfg&&(this._panelCfg.prefs={...this._panelCfg.prefs||{},...g});const u=_||this._hass&&this._hass.locale&&this._hass.locale.language;await this._loadPanelLang(u),this._render(),this._showToast(this._t("toast.preferences_saved",{},"Preferences saved"))}catch(u){this._showToast(this._t("toast.save_failed",{error:u.message||u},"Save failed: "+(u.message||u)),"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,_=l.dataset.rbacdev,p=l.value;i[c]||(i[c]={default:"none",devices:{}}),_==="__default__"?i[c].default=p:p&&p!=="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._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-search"){const a=n.getElementById("wd-store-q");this._storeSearch(a?a.value:"")}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));if(!o)return;this._storeDevice=o,this._storeProfile=null,this._storeView="device",this._storeProfiles=[],this._storeCycles=[],this._storeLoading=!0,this._render(),this._ws({type:`${y}/store_get_profiles`,entry_id:r,device_id:o.id}).then(i=>{!this._isActiveEntry(r)||this._storeView!=="device"||(this._storeProfiles=i&&i.items||[])}).catch(()=>{this._isActiveEntry(r)&&(this._storeProfiles=[])}).finally(()=>{this._isActiveEntry(r)&&(this._storeLoading=!1,this._render())})}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 g=i.error||"unavailable";this._showToast(this._t("toast.store_download_failed",{error:g},"Download failed: "+g),"error");return}const l=i&&i.profiles_adopted||0,c=i&&i.cycles_imported||0,_=i&&i.settings_applied||0;if(!l&&!c&&!_){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 p=i&&i.phases_applied||0;_?this._showToast(this._t("toast.store_device_downloaded_settings",{p:l,c,ph:p,s:_},`${l} program(s), ${c} recording(s), ${p} phase map(s), ${_} setting(s) added`)):p?this._showToast(this._t("toast.store_device_downloaded_phases",{p:l,c,ph:p},`${l} program(s), ${c} recording(s), ${p} 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-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),_=document.createElement("a");_.href=c,_.download=`washdata_export_${n.slice(0,8)}.json`,document.body.appendChild(_),_.click(),document.body.removeChild(_),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(_){this._showToast(this._t("toast.label_failed",{error:_.message||_},"Label failed: "+(_.message||_)),"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(_){this._showToast(this._t("toast.create_failed",{error:_.message||_},"Create failed: "+(_.message||_)),"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(_){this._showToast(this._t("msg.toast_save_failed",{error:_.message||_},"Save failed: "+(_.message||_)),"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||zt();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(_=>_.name):[])}else if(o==="real_cycles"||o==="reference_cycles"){const c=new Set;l&&(a.categories[o]&&a.categories[o].groups||[]).forEach(_=>_.cycles.forEach(p=>{p.id!=null&&c.add(String(p.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),_=c.length>0&&c.every(p=>l.has(p));c.forEach(p=>{_?l.delete(p):l.add(p)}),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 u=l.detail?`${l.error} - ${l.detail}`:l.error;this._showToast(this._t("toast.store_share_failed",{error:u},"Share failed: "+u),"error")}return}const c=l&&l.cycle_ids&&l.cycle_ids.length||0,_=l&&l.errors&&l.errors.length||0,p=l&&l.duplicates||0,g=l&&l.created!=null?l.created:c;if(!c){const u=l&&l.errors&&l.errors[0]||l&&l.detail||"upload_failed";this._showToast(this._t("toast.store_share_failed",{error:u},"Share failed: "+u),"error");return}this._modal=null,_?this._showToast(this._t("toast.store_device_shared_partial",{n:c,failed:_},`Shared ${c} cycle(s); ${_} could not be uploaded.`),"info"):p&&!g?this._showToast(this._t("toast.store_device_shared_all_dup",{n:p},`All ${p} cycle(s) were already in the community store.`),"info"):p?this._showToast(this._t("toast.store_device_shared_some_dup",{created:g,dup:p},`Shared ${g} cycle(s); ${p} were already in the store.`)):this._showToast(this._t("toast.store_device_shared",{n:g},`Shared ${g} 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(p=>p.checked).map(p=>p.value),c=r.getElementById("wd-cyc-rev-label")?.value??"",_=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!==_&&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!==_&&await this._fetchFeedbacks(s),await this._loadMlIndex(s),this._modal&&this._modal.cycleId===n&&(this._modal.ml=(this._mlById||{})[n]||this._modal.ml)}catch(p){this._showToast(this._t("msg.toast_save_failed",{error:p.message||p},"Save failed: "+(p.message||p)),"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-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,r=kt[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]=ee(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=kt[e];return r&&Array.isArray(r.def)&&r.def.length?r.def.slice():s}_conflictKeysForOpts(t,e){const s=Object.assign({},e||{},t),r=new Set;for(const n of Vt)if(n.check(s))for(const a of Object.keys(n.fieldErrors(s)))r.add(a);return r}_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}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 a of this._suggestions||[])a.key!=null&&a.suggested!=null&&(r[a.key]=+a.suggested);const n={};for(const a of Vt){if(!a.check(e))continue;const o=a.fieldErrors(e);for(const[i,l]of Object.entries(o)){const c=r[i],_=c!=null&&!a.check({...e,[i]:c})?{...l,suggFix:c}:l;(n[i]=n[i]||[]).push(_)}}return s.querySelectorAll("[data-cerr]").forEach(a=>{const o=a.dataset.cerr,i=n[o],l=s.querySelector(`.wd-field[data-field="${o}"]`);!i||!i.length?(a.hidden=!0,a.innerHTML="",l&&l.classList.remove("wd-has-conflict")):(a.hidden=!1,l&&l.classList.add("wd-has-conflict"),a.innerHTML=i.map(c=>{const _=this._t(c.msgKey,c.msgVars,c.msgFb);let p="";if(c.suggFix!=null){const g=+c.suggFix.toFixed(2);p=`${this._t("conflict.suggestion_resolves",{val:g},`Stage the pending suggestion (${g}) below to fix this`)}`}else if(c.fixVal!=null&&!isNaN(+c.fixVal)){const g=Number.isInteger(c.fixVal)?c.fixVal:+c.fixVal.toFixed(2);p=``}return`
    \u26A0 ${d(_)}${p}
    `}).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(p=>p.fixVal!=null&&!isNaN(+p.fixVal)&&+p.fixVal>0);if(!c)continue;const _=e.querySelector(`[data-opt="${i}"]`);_?_.value=c.fixVal:(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")}}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(o=>{const i=o.dataset.opt,l=kt[i],c=l&&l.type||o.dataset.ftype||"text";if(o.type==="checkbox"){s[i]=o.checked;return}if(c==="checkboxlist"){s[i]=this._collectCheckboxlist(o,i);return}if(c==="entitylist"){s[i]=Array.from(o.querySelectorAll(".wd-pill")).map(p=>p.dataset.val).filter(Boolean);return}if(c==="timerlist"){s[i]=Array.from(o.querySelectorAll(".wd-timer-row")).map(p=>({offset_minutes:parseFloat(p.querySelector('[data-field="offset_minutes"]').value)||0,message:(p.querySelector('[data-field="message"]').value||"").trim(),auto_pause:p.querySelector('[data-field="auto_pause"]').checked})).filter(p=>p.offset_minutes>0);return}const _=o.value;if(c==="number"){const p=String(_).trim();if(p===""){l&&l.clearable?s[i]=null:delete s[i];return}const g=parseFloat(p);isNaN(g)||(s[i]=g);return}if(c==="list"){s[i]=String(_).split(",").map(p=>p.trim()).filter(Boolean);return}if(c==="intlist"){s[i]=ee(_);return}if(c==="json"){const p=String(_).trim();if(!p){s[i]=[];return}try{s[i]=JSON.parse(p)}catch{this._invalidJson=i}return}if(c==="entity"||c==="device"){const p=String(_).trim();s[i]=p||null;return}s[i]=_});const r=this._optDefaults||{};for(const o of Object.keys(r))!(o in this._opts)&&o in s&&JSON.stringify(s[o])===JSON.stringify(r[o])&&delete s[o];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 o={},i={};for(const[l,c]of Object.entries(s)){if(a.has(l)){i[l]=c;continue}JSON.stringify(c)!==JSON.stringify((this._opts||{})[l])&&(o[l]=c)}this._pendingSettings={...this._pendingSettings,...i},Object.keys(o).length?await this._busyRun("save-settings",async()=>{try{await this._ws({type:`${y}/set_options`,entry_id:e.entry_id,options:o}),this._opts={...this._opts,...o},this._showToast(this._t("toast.saved_except_conflicts",{},"Saved. Fix the highlighted conflicts to save the rest."),"info")}catch(l){this._showToast(this._t("msg.toast_save_failed",{error:l.message||l},"Save failed: "+(l.message||l)),"error")}}):this._showToast(this._t("toast.settings_conflicts",{},"Fix the highlighted setting conflicts before saving."),"error");return}await this._busyRun("save-settings",async()=>{try{const o=JSON.parse(JSON.stringify(this._preCascadeOpts||this._opts||{}));if(await this._ws({type:`${y}/set_options`,entry_id:e.entry_id,options:s}),this._opts={...this._opts,...s},this._prevOpts=o,this._cascadePending={},this._preCascadeOpts=null,this._pendingSettings={},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(o){this._showToast(this._t("msg.toast_save_failed",{error:o.message||o},"Save failed: "+(o.message||o)),"error")}})}}customElements.get("ha-washdata-panel")||customElements.define("ha-washdata-panel",me);