/** Card editor for the Maintenance Supporter Lovelace card. */

import { LitElement, html, css, nothing } from "lit";
import { property, state } from "lit/decorators.js";
import { t, langOf } from "./styles";
import type { HomeAssistant, CardConfig, MaintenanceObjectResponse, SavedView } from "./types";

const STATUS_KEYS = ["overdue", "triggered", "due_soon", "ok"] as const;

export class MaintenanceSupporterCardEditor extends LitElement {
  @property({ attribute: false }) public hass!: HomeAssistant;
  @state() private _config: CardConfig = { type: "custom:maintenance-supporter-card" };
  @state() private _objects: MaintenanceObjectResponse[] = [];
  @state() private _loadingObjects = true;
  @state() private _loadError = false;
  @state() private _views: SavedView[] = [];

  private _objectsLoaded = false;

  private get _lang(): string {
    return langOf(this.hass);
  }

  setConfig(config: CardConfig): void {
    this._config = { ...config };
  }

  updated(changedProps: Map<string, unknown>): void {
    super.updated(changedProps);
    if (changedProps.has("hass") && this.hass && !this._objectsLoaded) {
      this._objectsLoaded = true;
      this._loadObjects();
    }
  }

  private async _loadObjects(): Promise<void> {
    try {
      const result = await this.hass.connection.sendMessagePromise({
        type: "maintenance_supporter/objects",
      }) as { objects: MaintenanceObjectResponse[] };
      this._objects = result.objects || [];
      this._loadError = false;
    } catch {
      this._objects = [];
      this._loadError = true;
    }
    this._loadingObjects = false;
    // Saved views feed the scope dropdown; best-effort — the editor works
    // without them (the dropdown just stays at "none").
    try {
      const res = await this.hass.connection.sendMessagePromise({
        type: "maintenance_supporter/views/list",
      }) as { views: SavedView[] };
      this._views = res.views || [];
    } catch {
      this._views = [];
    }
  }

  private _valueChanged(key: string, value: unknown): void {
    const newConfig = { ...this._config, [key]: value };
    // Drop empty arrays / empty strings so the saved YAML stays clean
    if ((Array.isArray(value) && value.length === 0) || value === "") {
      delete (newConfig as Record<string, unknown>)[key];
    }
    this._config = newConfig;
    this.dispatchEvent(
      new CustomEvent("config-changed", { detail: { config: newConfig } })
    );
  }

  private _toggleStatus(status: string, on: boolean): void {
    const current = new Set(this._config.filter_status || []);
    if (on) current.add(status); else current.delete(status);
    this._valueChanged("filter_status", [...current]);
  }

  private _toggleObject(name: string, on: boolean): void {
    const current = new Set(this._config.filter_objects || []);
    if (on) current.add(name); else current.delete(name);
    this._valueChanged("filter_objects", [...current]);
  }

  private _toggleLabel(label: string, on: boolean): void {
    const current = new Set(this._config.filter_labels || []);
    if (on) current.add(label); else current.delete(label);
    this._valueChanged("filter_labels", [...current]);
  }

  private _toggleArea(areaId: string, on: boolean): void {
    const current = new Set(this._config.filter_areas || []);
    if (on) current.add(areaId); else current.delete(areaId);
    this._valueChanged("filter_areas", [...current]);
  }

  private _togglePriority(priority: string, on: boolean): void {
    const current = new Set(this._config.filter_priority || []);
    if (on) current.add(priority); else current.delete(priority);
    this._valueChanged("filter_priority", [...current]);
  }

  private _onEntitiesChanged = (e: CustomEvent<{ value: string[] }>): void => {
    this._valueChanged("entity_ids", e.detail.value || []);
  };

  render() {
    const L = this._lang;
    const selectedStatuses = new Set(this._config.filter_status || []);
    const selectedObjects = new Set(this._config.filter_objects || []);
    const objectNames = [...this._objects]
      .map((o) => o.object.name)
      .sort((a, b) => a.localeCompare(b));
    // Areas (C8): offer exactly the areas that actually hold an object — an
    // area with nothing in it could only ever empty the card. Names resolve
    // through hass.areas; an area HA no longer knows falls back to its raw id
    // so an existing config stays visible (and un-checkable) instead of
    // vanishing silently.
    const selectedAreas = new Set(this._config.filter_areas || []);
    const areaIds = [
      ...new Set(
        this._objects
          .map((o) => o.object.area_id)
          .filter((a): a is string => !!a)
          .concat([...selectedAreas]),
      ),
    ];
    const areaName = (id: string): string => this.hass?.areas?.[id]?.name || id;
    const areas = areaIds
      .map((id) => ({ id, name: areaName(id) }))
      .sort((a, b) => a.name.localeCompare(b.name));
    const selectedLabels = new Set(this._config.filter_labels || []);
    const selectedPriorities = new Set(this._config.filter_priority || []);
    // Labels are free-form per task, so the picker offers exactly the ones in
    // use — an empty list simply hides the section.
    const labelNames = [
      ...new Set(this._objects.flatMap((o) => o.tasks.flatMap((tk) => tk.labels || []))),
    ].sort((a, b) => a.localeCompare(b));
    // Build the list of OUR sensor + binary_sensor entity_ids so the
    // ha-entities-picker only shows maintenance_supporter entities, not
    // every sensor in HA.
    const ourEntities: string[] = [];
    for (const o of this._objects) {
      for (const t of o.tasks) {
        if (t.sensor_entity_id) ourEntities.push(t.sensor_entity_id);
        if (t.binary_sensor_entity_id) ourEntities.push(t.binary_sensor_entity_id);
      }
    }

    return html`
      <div class="editor">
        <ha-textfield
          label="${t("card_title", L)}"
          .value=${this._config.title || ""}
          @input=${(e: Event) =>
            this._valueChanged("title", (e.target as HTMLInputElement).value)}
        ></ha-textfield>

        <!-- Status filter (chip row) -->
        <div class="field">
          <div class="field-label">${t("card_filter_status", L)}</div>
          <div class="chip-row">
            ${STATUS_KEYS.map((s) => html`
              <label class="chip ${selectedStatuses.has(s) ? "active" : ""}">
                <input type="checkbox"
                  .checked=${selectedStatuses.has(s)}
                  @change=${(e: Event) => this._toggleStatus(s, (e.target as HTMLInputElement).checked)} />
                ${t(s, L)}
              </label>
            `)}
          </div>
          <div class="field-help">${t("card_filter_status_help", L)}</div>
        </div>

        <!-- Object filter (multi-checkbox) -->
        <div class="field">
          <div class="field-label">${t("card_filter_objects", L)}</div>
          ${this._loadingObjects
            ? html`<div class="field-help">${t("card_loading_objects", L)}</div>`
            : this._loadError
              ? html`<div class="field-help error-text">${t("card_load_error", L)}</div>`
              : objectNames.length === 0
                ? html`<div class="field-help">${t("no_objects", L)}</div>`
                : html`
                <div class="object-list">
                  ${objectNames.map((name) => html`
                    <label class="object-row">
                      <input type="checkbox"
                        .checked=${selectedObjects.has(name)}
                        @change=${(e: Event) => this._toggleObject(name, (e.target as HTMLInputElement).checked)} />
                      <span>${name}</span>
                    </label>
                  `)}
                </div>
                <div class="field-help">${t("card_filter_objects_help", L)}</div>
              `
          }
        </div>
        <!-- Area filter (C8): selects whole objects by the room they sit in.
             Hidden while no object has an area — the section would be an
             empty box otherwise. -->
        ${areas.length ? html`
        <div class="field">
          <div class="field-label">${t("card_filter_areas", L)}</div>
          <div class="object-list">
            ${areas.map((a) => html`
              <label class="object-row">
                <input type="checkbox"
                  .checked=${selectedAreas.has(a.id)}
                  @change=${(e: Event) => this._toggleArea(a.id, (e.target as HTMLInputElement).checked)} />
                <span>${a.name}</span>
              </label>
            `)}
          </div>
          <div class="field-help">${t("card_filter_areas_help", L)}</div>
        </div>` : nothing}
        ${labelNames.length ? html`
        <div class="field">
          <div class="field-label">${t("labels", L)}</div>
          <div class="object-list">
            ${labelNames.map((name) => html`
              <label class="object-row">
                <input type="checkbox"
                  .checked=${selectedLabels.has(name)}
                  @change=${(e: Event) => this._toggleLabel(name, (e.target as HTMLInputElement).checked)} />
                <span>${name}</span>
              </label>
            `)}
          </div>
        </div>` : nothing}
        <div class="field">
          <div class="field-label">${t("priority", L)}</div>
          <div class="object-list">
            ${["high", "normal", "low"].map((pr) => html`
              <label class="object-row">
                <input type="checkbox"
                  .checked=${selectedPriorities.has(pr)}
                  @change=${(e: Event) => this._togglePriority(pr, (e.target as HTMLInputElement).checked)} />
                <span>${t(`priority_${pr}`, L)}</span>
              </label>
            `)}
          </div>
          <div class="field-help">${t("card_filter_priority_help", L)}</div>
        </div>

        <!-- Entity-id filter (HA-native pattern). Limited to our integration's
             sensor + binary_sensor entities via includeEntities so the picker
             stays usable on installs with thousands of entities. -->
        <div class="field">
          <div class="field-label">${t("card_filter_entities", L)}</div>
          <ha-entities-picker
            .hass=${this.hass}
            .value=${this._config.entity_ids || []}
            .includeDomains=${["sensor", "binary_sensor"]}
            .includeEntities=${ourEntities}
            @value-changed=${this._onEntitiesChanged}
          ></ha-entities-picker>
          <div class="field-help">${t("card_filter_entities_help", L)}</div>
        </div>

        <!-- Saved-view scope (v2.26): applies the view's status/user/label
             filters on top of everything above. Hidden while no views exist —
             views are created in the panel toolbar, not here. -->
        ${this._views.length > 0
          ? html`
              <div class="field">
                <div class="field-label">${t("card_saved_view", L)}</div>
                <select
                  class="view-select"
                  .value=${this._config.view_id || ""}
                  @change=${(e: Event) =>
                    this._valueChanged("view_id", (e.target as HTMLSelectElement).value)}
                >
                  <option value="" ?selected=${!this._config.view_id}>
                    ${t("card_saved_view_none", L)}
                  </option>
                  ${this._views.map(
                    (v) => html`<option value=${v.id} ?selected=${this._config.view_id === v.id}>
                      ${v.name}
                    </option>`
                  )}
                </select>
                <div class="field-help">${t("card_saved_view_help", L)}</div>
              </div>
            `
          : nothing}

        <ha-formfield label="${t("card_show_header", L)}">
          <ha-switch
            .checked=${this._config.show_header !== false}
            @change=${(e: Event) =>
              this._valueChanged("show_header", (e.target as HTMLInputElement).checked)}
          ></ha-switch>
        </ha-formfield>

        <ha-formfield label="${t("card_show_actions", L)}">
          <ha-switch
            .checked=${this._config.show_actions !== false}
            @change=${(e: Event) =>
              this._valueChanged("show_actions", (e.target as HTMLInputElement).checked)}
          ></ha-switch>
        </ha-formfield>

        <ha-formfield label="${t("responsible_user", L)}">
          <ha-switch
            .checked=${this._config.show_assignee !== false}
            @change=${(e: Event) =>
              this._valueChanged("show_assignee", (e.target as HTMLInputElement).checked)}
          ></ha-switch>
        </ha-formfield>

        <ha-formfield label="${t("documents", L)}">
          <ha-switch
            .checked=${this._config.show_documents !== false}
            @change=${(e: Event) =>
              this._valueChanged("show_documents", (e.target as HTMLInputElement).checked)}
          ></ha-switch>
        </ha-formfield>

        <ha-formfield label="${t("card_compact", L)}">
          <ha-switch
            .checked=${this._config.compact || false}
            @change=${(e: Event) =>
              this._valueChanged("compact", (e.target as HTMLInputElement).checked)}
          ></ha-switch>
        </ha-formfield>

        <ha-textfield
          label="${t("card_max_items", L)}"
          type="number"
          .value=${String(this._config.max_items || 0)}
          @input=${(e: Event) =>
            this._valueChanged("max_items", parseInt((e.target as HTMLInputElement).value, 10) || 0)}
        ></ha-textfield>
        ${nothing}
      </div>
    `;
  }

  static styles = css`
    .editor {
      display: flex;
      flex-direction: column;
      gap: 16px;
      padding: 16px;
    }
    ha-textfield { display: block; }
    .field {
      display: flex;
      flex-direction: column;
      gap: 6px;
    }
    .field-label {
      font-size: 13px;
      color: var(--secondary-text-color);
      font-weight: 500;
    }
    .field-help {
      font-size: 12px;
      color: var(--secondary-text-color);
      font-style: italic;
    }
    .field-help.error-text {
      color: var(--error-color, #f44336);
      font-style: normal;
    }
    .chip-row {
      display: flex;
      gap: 6px;
      flex-wrap: wrap;
    }
    .chip {
      display: inline-flex;
      align-items: center;
      gap: 4px;
      padding: 4px 10px;
      border: 1px solid var(--divider-color);
      border-radius: 14px;
      cursor: pointer;
      font-size: 13px;
      user-select: none;
      transition: background 0.15s, border-color 0.15s;
    }
    .chip:hover {
      background: var(--secondary-background-color);
    }
    .chip.active {
      background: var(--primary-color);
      color: var(--text-primary-color);
      border-color: var(--primary-color);
    }
    .chip input {
      display: none;
    }
    .object-list {
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
      gap: 4px 12px;
      padding: 6px 0;
    }
    .object-row {
      display: flex;
      align-items: center;
      gap: 6px;
      padding: 3px 0;
      font-size: 13px;
      cursor: pointer;
    }
    .object-row input { cursor: pointer; }
    .view-select {
      padding: 8px;
      border: 1px solid var(--divider-color);
      border-radius: 4px;
      background: var(--card-background-color, #fff);
      color: var(--primary-text-color);
      font-size: 14px;
      max-width: 320px;
    }
  `;
}

// Module-bottom registration so esbuild's tree-shaker doesn't drop the
// element class when the only reference looks "type-only" (the
// @customElement decorator pattern triggered exactly that bug for the
// dialog components — same fix applied here for the card editor).
if (!customElements.get("maintenance-supporter-card-editor")) {
  customElements.define(
    "maintenance-supporter-card-editor",
    MaintenanceSupporterCardEditor,
  );
}
