import type { HassEntity } from "home-assistant-js-websocket";
import type { CSSResultGroup } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../common/dom/fire_event";
import { DirtyStateProviderMixin } from "../../../mixins/dirty-state-provider-mixin";
import "../../../components/entity/ha-entity-picker";
import type { HaEntityPicker } from "../../../components/entity/ha-entity-picker";
import "../../../components/ha-alert";
import "../../../components/ha-aliases-editor";
import "../../../components/ha-button";
import "../../../components/ha-dialog";
import "../../../components/ha-dialog-footer";
import "../../../components/ha-expansion-panel";
import "../../../components/ha-floor-picker";
import "../../../components/ha-icon-picker";
import "../../../components/ha-labels-picker";
import "../../../components/ha-picture-upload";
import type { HaPictureUpload } from "../../../components/ha-picture-upload";
import "../../../components/ha-settings-row";
import "../../../components/ha-suggest-with-ai-button";
import type { SuggestWithAIGenerateTask } from "../../../components/ha-suggest-with-ai-button";
import "../../../components/input/ha-input";
import type { HaInput } from "../../../components/input/ha-input";
import type { GenDataTaskResult } from "../../../data/ai_task";
import type {
  AreaRegistryEntry,
  AreaRegistryEntryMutableParams,
} from "../../../data/area/area_registry";
import { deleteAreaRegistryEntry } from "../../../data/area/area_registry";
import {
  SENSOR_DEVICE_CLASS_HUMIDITY,
  SENSOR_DEVICE_CLASS_TEMPERATURE,
} from "../../../data/sensor";
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
import type { CropOptions } from "../../../dialogs/image-cropper-dialog/show-image-cropper-dialog";
import type { HassDialog } from "../../../dialogs/make-dialog-manager";
import { haStyleDialog } from "../../../resources/styles";
import type { HomeAssistant, ValueChangedEvent } from "../../../types";
import {
  type MetadataSuggestionInclude,
  type MetadataSuggestionResult,
  generateMetadataSuggestionTask,
  processMetadataSuggestion,
} from "../common/suggest-metadata-ai";
import { fetchLabels } from "../common/suggest-metadata-helpers";
import { buildAreaMetadataInspirations } from "../common/suggest-metadata-inspirations";
import type { AreaRegistryDetailDialogParams } from "./show-dialog-area-registry-detail";

const cropOptions: CropOptions = {
  round: false,
};

const SENSOR_DOMAINS = ["sensor"];
const TEMPERATURE_DEVICE_CLASSES = [SENSOR_DEVICE_CLASS_TEMPERATURE];
const HUMIDITY_DEVICE_CLASSES = [SENSOR_DEVICE_CLASS_HUMIDITY];

interface AreaFormState {
  name: string;
  aliases: string[];
  labels: string[];
  picture: string | null;
  icon: string | null;
  floor: string | null;
  temperatureEntity: string | null;
  humidityEntity: string | null;
}

@customElement("dialog-area-registry-detail")
class DialogAreaDetail
  extends DirtyStateProviderMixin<AreaFormState>()(LitElement)
  implements HassDialog<AreaRegistryDetailDialogParams>
{
  @property({ attribute: false }) public hass!: HomeAssistant;

  @state() private _name!: string;

  @state() private _aliases!: string[];

  @state() private _labels!: string[];

  @state() private _picture!: string | null;

  @state() private _icon!: string | null;

  @state() private _floor!: string | null;

  @state() private _temperatureEntity!: string | null;

  @state() private _humidityEntity!: string | null;

  @state() private _error?: string;

  @state() private _params?: AreaRegistryDetailDialogParams;

  @state() private _submitting = false;

  @state() private _open = false;

  @state() private _suggestionInclude: MetadataSuggestionInclude = {
    name: true,
    labels: true,
    floor: true,
  };

  public async showDialog(
    params: AreaRegistryDetailDialogParams
  ): Promise<void> {
    this._params = params;
    this._error = undefined;
    if (this._params.entry) {
      this._name = this._params.entry.name;
      this._aliases = this._params.entry.aliases;
      this._labels = this._params.entry.labels;
      this._picture = this._params.entry.picture;
      this._icon = this._params.entry.icon;
      this._floor = this._params.entry.floor_id;
      this._temperatureEntity = this._params.entry.temperature_entity_id;
      this._humidityEntity = this._params.entry.humidity_entity_id;
    } else {
      this._name = this._params.suggestedName || "";
      this._aliases = [];
      this._labels = [];
      this._picture = null;
      this._icon = null;
      this._floor = null;
      this._temperatureEntity = null;
      this._humidityEntity = null;
    }
    this._open = true;
    this._initDirtyTracking({ type: "deep" }, this._currentState());
    await this.updateComplete;
  }

  private _currentState(): AreaFormState {
    return {
      name: this._name,
      aliases: this._aliases,
      labels: this._labels,
      picture: this._picture,
      icon: this._icon,
      floor: this._floor,
      temperatureEntity: this._temperatureEntity,
      humidityEntity: this._humidityEntity,
    };
  }

  public closeDialog(): boolean {
    this._open = false;
    return true;
  }

  private _dialogClosed(): void {
    this._error = "";
    this._params = undefined;
    fireEvent(this, "dialog-closed", { dialog: this.localName });
  }

  private _renderSettings(entry: AreaRegistryEntry | undefined) {
    return html`
      ${entry
        ? html`
            <ha-settings-row>
              <span slot="heading">
                ${this.hass.localize("ui.panel.config.areas.editor.area_id")}
              </span>
              <span slot="description"> ${entry.area_id} </span>
            </ha-settings-row>
          `
        : nothing}

      <ha-input
        autofocus
        .value=${this._name}
        @input=${this._nameChanged}
        .label=${this.hass.localize("ui.panel.config.areas.editor.name")}
        .validationMessage=${this.hass.localize(
          "ui.panel.config.areas.editor.name_required"
        )}
        auto-validate
        required
      ></ha-input>

      <ha-icon-picker
        .hass=${this.hass}
        .value=${this._icon}
        @value-changed=${this._iconChanged}
        .label=${this.hass.localize("ui.panel.config.areas.editor.icon")}
      ></ha-icon-picker>

      <ha-floor-picker
        .hass=${this.hass}
        .value=${this._floor}
        @value-changed=${this._floorChanged}
        .label=${this.hass.localize("ui.panel.config.areas.editor.floor")}
      ></ha-floor-picker>

      <ha-labels-picker
        .label=${this.hass.localize("ui.components.label-picker.labels")}
        .hass=${this.hass}
        .value=${this._labels}
        @value-changed=${this._labelsChanged}
        .placeholder=${this.hass.localize(
          "ui.panel.config.areas.editor.add_labels"
        )}
      ></ha-labels-picker>

      <ha-picture-upload
        .hass=${this.hass}
        .value=${this._picture}
        crop
        select-media
        .cropOptions=${cropOptions}
        @change=${this._pictureChanged}
      ></ha-picture-upload>
    `;
  }

  private _renderAliasExpansion() {
    return html`
      <ha-expansion-panel
        outlined
        .header=${this.hass.localize(
          "ui.panel.config.areas.editor.aliases_section"
        )}
        expanded
      >
        <div class="content">
          <p class="description">
            ${this.hass.localize(
              "ui.panel.config.areas.editor.aliases_description"
            )}
          </p>
          <ha-aliases-editor
            .aliases=${this._aliases}
            @value-changed=${this._aliasesChanged}
          ></ha-aliases-editor>
        </div>
      </ha-expansion-panel>
    `;
  }

  private _renderRelatedEntitiesExpansion() {
    return html`
      <ha-expansion-panel
        outlined
        .header=${this.hass.localize(
          "ui.panel.config.areas.editor.related_entities_section"
        )}
        expanded
      >
        <div class="content">
          <ha-entity-picker
            .hass=${this.hass}
            .label=${this.hass.localize(
              "ui.panel.config.areas.editor.temperature_entity"
            )}
            .helper=${this.hass.localize(
              "ui.panel.config.areas.editor.temperature_entity_description"
            )}
            .value=${this._temperatureEntity}
            .includeDomains=${SENSOR_DOMAINS}
            .includeDeviceClasses=${TEMPERATURE_DEVICE_CLASSES}
            .entityFilter=${this._areaEntityFilter}
            @value-changed=${this._sensorChanged}
          ></ha-entity-picker>

          <ha-entity-picker
            .hass=${this.hass}
            .label=${this.hass.localize(
              "ui.panel.config.areas.editor.humidity_entity"
            )}
            .helper=${this.hass.localize(
              "ui.panel.config.areas.editor.humidity_entity_description"
            )}
            .value=${this._humidityEntity}
            .includeDomains=${SENSOR_DOMAINS}
            .includeDeviceClasses=${HUMIDITY_DEVICE_CLASSES}
            .entityFilter=${this._areaEntityFilter}
            @value-changed=${this._sensorChanged}
          ></ha-entity-picker>
        </div>
      </ha-expansion-panel>
    `;
  }

  private async _getLabelNames(): Promise<string[]> {
    if (!this._labels.length) {
      return [];
    }
    const labels = await fetchLabels(this.hass.connection);
    return this._labels
      .map((labelId) => labels[labelId])
      .filter((name): name is string => Boolean(name));
  }

  private _generateTask = async (): Promise<SuggestWithAIGenerateTask> => {
    this._suggestionInclude = {
      ...this._suggestionInclude,
      name: this._name.trim() === "",
    };

    return generateMetadataSuggestionTask<{
      name: string;
      aliases: string[];
      labels: string[];
      floor: string | null;
      temperature_entity: string | null;
      humidity_entity: string | null;
    }>(
      this.hass.connection,
      this.hass.language,
      "area",
      {
        name: this._name,
        aliases: this._aliases,
        labels: await this._getLabelNames(),
        floor: this._floor ? this.hass.floors?.[this._floor]?.name : null,
        temperature_entity: this._temperatureEntity
          ? (this.hass.states[this._temperatureEntity]?.attributes
              ?.friendly_name ?? null)
          : null,
        humidity_entity: this._humidityEntity
          ? (this.hass.states[this._humidityEntity]?.attributes
              ?.friendly_name ?? null)
          : null,
      },
      await buildAreaMetadataInspirations(this.hass.connection),
      this._suggestionInclude
    );
  };

  private async _handleSuggestion(
    event: CustomEvent<GenDataTaskResult<MetadataSuggestionResult>>
  ) {
    const result = event.detail;
    const processed = await processMetadataSuggestion(
      this.hass.connection,
      "area",
      result,
      this._suggestionInclude
    );

    if (processed.name) {
      this._name = processed.name;
    }

    if (processed.labels?.length) {
      this._labels = processed.labels;
    }

    if (processed.floor) {
      this._floor = processed.floor;
    }

    this._updateDirtyState(this._currentState());
  }

  protected render() {
    if (!this._params) {
      return nothing;
    }
    const entry = this._params.entry;
    const nameInvalid = !this._isNameValid();
    const isNew = !entry;

    return html`
      <ha-dialog
        .hass=${this.hass}
        .open=${this._open}
        header-title=${entry
          ? this.hass.localize("ui.panel.config.areas.editor.update_area")
          : this.hass.localize("ui.panel.config.areas.editor.create_area")}
        .preventScrimClose=${this.isDirtyState}
        @closed=${this._dialogClosed}
      >
        <ha-suggest-with-ai-button
          slot="headerActionItems"
          .hass=${this.hass}
          .generateTask=${this._generateTask}
          @suggestion=${this._handleSuggestion}
        ></ha-suggest-with-ai-button>
        <div>
          ${this._error
            ? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
            : ""}
          <div class="form">
            ${this._renderSettings(entry)} ${this._renderAliasExpansion()}
            ${!isNew ? this._renderRelatedEntitiesExpansion() : nothing}
          </div>
        </div>
        <ha-dialog-footer slot="footer">
          ${!isNew
            ? html`
                <ha-button
                  slot="secondaryAction"
                  variant="danger"
                  appearance="plain"
                  @click=${this._deleteArea}
                  .disabled=${this._submitting}
                >
                  ${this.hass.localize("ui.common.delete")}
                </ha-button>
              `
            : html`<ha-button
                appearance="plain"
                slot="secondaryAction"
                @click=${this.closeDialog}
              >
                ${this.hass.localize("ui.common.cancel")}
              </ha-button>`}
          <ha-button
            slot="primaryAction"
            @click=${this._updateEntry}
            .disabled=${nameInvalid ||
            this._submitting ||
            (!!this._params?.entry && !this.isDirtyState)}
          >
            ${entry
              ? this.hass.localize("ui.common.save")
              : this.hass.localize("ui.common.create")}
          </ha-button>
        </ha-dialog-footer>
      </ha-dialog>
    `;
  }

  private _isNameValid() {
    return this._name.trim() !== "";
  }

  private _areaEntityFilter = (stateObj: HassEntity): boolean => {
    const entityReg = this.hass.entities[stateObj.entity_id];
    if (!entityReg) {
      return false;
    }
    const areaId = this._params!.entry!.area_id;
    if (entityReg.area_id === areaId) {
      return true;
    }
    if (!entityReg.device_id) {
      return false;
    }
    const deviceReg = this.hass.devices[entityReg.device_id];
    return deviceReg && deviceReg.area_id === areaId;
  };

  private _nameChanged(ev: InputEvent) {
    this._error = undefined;
    this._name = (ev.target as HaInput).value ?? "";
    this._updateDirtyState(this._currentState());
  }

  private _floorChanged(ev) {
    this._error = undefined;
    this._floor = ev.detail.value;
    this._updateDirtyState(this._currentState());
  }

  private _iconChanged(ev) {
    this._error = undefined;
    this._icon = ev.detail.value;
    this._updateDirtyState(this._currentState());
  }

  private _labelsChanged(ev) {
    this._error = undefined;
    this._labels = ev.detail.value;
    this._updateDirtyState(this._currentState());
  }

  private _pictureChanged(ev: ValueChangedEvent<string | null>) {
    this._error = undefined;
    this._picture = (ev.target as HaPictureUpload).value;
    this._updateDirtyState(this._currentState());
  }

  private _aliasesChanged(ev: CustomEvent): void {
    this._aliases = ev.detail.value;
    this._updateDirtyState(this._currentState());
  }

  private _sensorChanged(ev: CustomEvent): void {
    const deviceClass = (ev.target as HaEntityPicker).includeDeviceClasses![0];
    const key = `_${deviceClass}Entity`;
    this[key] = ev.detail.value || null;
    this._updateDirtyState(this._currentState());
  }

  private async _updateEntry() {
    const create = !this._params!.entry;
    this._submitting = true;
    try {
      const values: AreaRegistryEntryMutableParams = {
        name: this._name.trim(),
        picture: this._picture || (create ? undefined : null),
        icon: this._icon || (create ? undefined : null),
        floor_id: this._floor || (create ? undefined : null),
        labels: this._labels || null,
        aliases: this._aliases,
        temperature_entity_id: this._temperatureEntity,
        humidity_entity_id: this._humidityEntity,
      };
      if (create) {
        await this._params!.createEntry!(values);
      } else {
        await this._params!.updateEntry!(values);
      }
      this._markDirtyStateClean();
      this.closeDialog();
    } catch (err: any) {
      this._error =
        err.message ||
        this.hass.localize("ui.panel.config.areas.editor.unknown_error");
    } finally {
      this._submitting = false;
    }
  }

  private async _deleteArea() {
    if (!this._params?.entry) {
      return;
    }

    const confirmed = await showConfirmationDialog(this, {
      title: this.hass.localize(
        "ui.panel.config.areas.delete.confirmation_title",
        { name: this._params.entry.name }
      ),
      text: this.hass.localize(
        "ui.panel.config.areas.delete.confirmation_text"
      ),
      dismissText: this.hass.localize("ui.common.cancel"),
      confirmText: this.hass.localize("ui.common.delete"),
      destructive: true,
    });
    if (!confirmed) {
      return;
    }

    await deleteAreaRegistryEntry(this.hass!, this._params!.entry!.area_id);
    this.closeDialog();
  }

  static get styles(): CSSResultGroup {
    return [
      haStyleDialog,
      css`
        ha-expansion-panel {
          --expansion-panel-content-padding: 0;
        }
        ha-aliases-editor,
        ha-entity-picker,
        ha-floor-picker,
        ha-icon-picker,
        ha-labels-picker,
        ha-picture-upload,
        ha-expansion-panel {
          display: block;
          margin-bottom: var(--ha-space-4);
        }
        .content {
          padding: var(--ha-space-3);
        }
        .description {
          margin: 0 0 var(--ha-space-4) 0;
        }
        ha-suggest-with-ai-button {
          margin: var(--ha-space-2) var(--ha-space-4);
        }
      `,
    ];
  }
}

declare global {
  interface HTMLElementTagNameMap {
    "dialog-area-registry-detail": DialogAreaDetail;
  }
}
