import { mdiChevronLeft, mdiMenuDown } from "@mdi/js";
import type { CSSResultGroup, PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../common/dom/fire_event";
import { computeDomain } from "../../common/entity/compute_domain";
import { formatLanguageCode } from "../../common/language/format_language";
import "../../components/chips/ha-assist-chip";
import "../../components/ha-dialog";
import "../../components/ha-dropdown";
import type { HaDropdownSelectEvent } from "../../components/ha-dropdown";
import "../../components/ha-dropdown-item";
import { getLanguageOptions } from "../../components/ha-language-picker";
import type { AssistSatelliteConfiguration } from "../../data/assist_satellite";
import { fetchAssistSatelliteConfiguration } from "../../data/assist_satellite";
import { getLanguageScores } from "../../data/conversation";
import { UNAVAILABLE } from "../../data/entity/entity";
import type { EntityRegistryDisplayEntry } from "../../data/entity/entity_registry";
import { haStyleDialog } from "../../resources/styles";
import type { HomeAssistant } from "../../types";
import type { VoiceAssistantSetupDialogParams } from "./show-voice-assistant-setup-dialog";
import "./voice-assistant-setup-step-area";
import "./voice-assistant-setup-step-change-wake-word";
import "./voice-assistant-setup-step-check";
import "./voice-assistant-setup-step-cloud";
import "./voice-assistant-setup-step-local";
import "./voice-assistant-setup-step-pipeline";
import "./voice-assistant-setup-step-success";
import "./voice-assistant-setup-step-update";
import "./voice-assistant-setup-step-wake-word";

export const enum STEP {
  INIT,
  UPDATE,
  CHECK,
  WAKEWORD,
  AREA,
  PIPELINE,
  SUCCESS,
  CLOUD,
  LOCAL,
  CHANGE_WAKEWORD,
}

@customElement("ha-voice-assistant-setup-dialog")
export class HaVoiceAssistantSetupDialog extends LitElement {
  @property({ attribute: false }) public hass!: HomeAssistant;

  @state() private _params?: VoiceAssistantSetupDialogParams;

  @state() private _open = false;

  @state() private _step: STEP = STEP.INIT;

  @state() private _assistConfiguration?: AssistSatelliteConfiguration;

  @state() private _error?: string;

  @state() private _language?: string;

  @state() private _languages: string[] = [];

  @state() private _localOption?: string;

  private _previousSteps: STEP[] = [];

  private _nextStep?: STEP;

  public async showDialog(
    params: VoiceAssistantSetupDialogParams
  ): Promise<void> {
    this._params = params;
    this._open = true;

    await this._fetchAssistConfiguration();

    this._step = STEP.UPDATE;
  }

  public closeDialog(): void {
    this._open = false;
  }

  protected willUpdate(changedProps: PropertyValues) {
    if (changedProps.has("_step") && this._step === STEP.PIPELINE) {
      this._getLanguages();
    }
  }

  private _dialogClosed() {
    this._open = false;
    this._params = undefined;
    this._assistConfiguration = undefined;
    this._previousSteps = [];
    this._nextStep = undefined;
    this._step = STEP.INIT;
    this._language = undefined;
    this._languages = [];
    this._localOption = undefined;
    fireEvent(this, "dialog-closed", { dialog: this.localName });
  }

  private _deviceEntities = memoizeOne(
    (
      deviceId: string,
      entities: HomeAssistant["entities"]
    ): EntityRegistryDisplayEntry[] =>
      Object.values(entities).filter((entity) => entity.device_id === deviceId)
  );

  private _findDomainEntityId = memoizeOne(
    (
      deviceId: string,
      entities: HomeAssistant["entities"],
      domain: string
    ): string | undefined => {
      const deviceEntities = this._deviceEntities(deviceId, entities);
      return deviceEntities.find(
        (ent) => computeDomain(ent.entity_id) === domain
      )?.entity_id;
    }
  );

  protected render() {
    if (!this._params) {
      return nothing;
    }

    const assistSatelliteEntityId = this._findDomainEntityId(
      this._params.deviceId,
      this.hass.entities,
      "assist_satellite"
    );

    const assistEntityState = assistSatelliteEntityId
      ? this.hass.states[assistSatelliteEntityId]
      : undefined;

    const hideNavigationIcon =
      this._step === STEP.LOCAL ||
      (this._step === STEP.UPDATE && !this._previousSteps.length);

    return html`
      <ha-dialog
        .open=${this._open}
        header-title="Voice Satellite setup"
        prevent-scrim-close
        @closed=${this._dialogClosed}
      >
        ${hideNavigationIcon
          ? html`<span slot="headerNavigationIcon"></span>`
          : this._previousSteps.length
            ? html`<ha-icon-button
                slot="headerNavigationIcon"
                .label=${this.hass.localize("ui.common.back") ?? "Back"}
                .path=${mdiChevronLeft}
                @click=${this._goToPreviousStep}
              ></ha-icon-button>`
            : nothing}
        ${this._step === STEP.WAKEWORD || this._step === STEP.AREA
          ? html`<ha-button
              @click=${this._goToNextStep}
              class="skip-btn"
              slot="headerActionItems"
              >${this.hass.localize(
                "ui.panel.config.voice_assistants.satellite_wizard.skip"
              )}</ha-button
            >`
          : this._step === STEP.PIPELINE
            ? this._language
              ? html`<ha-dropdown
                  slot="headerActionItems"
                  @wa-select=${this._handlePickLanguage}
                >
                  <ha-assist-chip
                    .label=${formatLanguageCode(
                      this._language,
                      this.hass.locale
                    )}
                    slot="trigger"
                  >
                    <ha-svg-icon
                      slot="trailing-icon"
                      .path=${mdiMenuDown}
                    ></ha-svg-icon
                  ></ha-assist-chip>
                  ${getLanguageOptions(
                    this._languages,
                    false,
                    false,
                    this.hass.locale
                  ).map(
                    (lang) =>
                      html`<ha-dropdown-item
                        .value=${lang.id}
                        .selected=${this._language === lang.id}
                      >
                        ${lang.primary}
                      </ha-dropdown-item>`
                  )}
                </ha-dropdown>`
              : nothing
            : nothing}
        <div
          class="content"
          @next-step=${this._goToNextStep}
          @prev-step=${this._goToPreviousStep}
        >
          ${this._step === STEP.UPDATE
            ? html`<ha-voice-assistant-setup-step-update
                .hass=${this.hass}
                .updateEntityId=${this._findDomainEntityId(
                  this._params.deviceId,
                  this.hass.entities,
                  "update"
                )}
              ></ha-voice-assistant-setup-step-update>`
            : this._error
              ? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
              : assistEntityState?.state === UNAVAILABLE
                ? html`<ha-alert alert-type="error"
                    >${this.hass.localize(
                      "ui.panel.config.voice_assistants.satellite_wizard.not_available"
                    )}</ha-alert
                  >`
                : this._step === STEP.CHECK
                  ? html`<ha-voice-assistant-setup-step-check
                      .hass=${this.hass}
                      .assistEntityId=${assistSatelliteEntityId}
                    ></ha-voice-assistant-setup-step-check>`
                  : this._step === STEP.WAKEWORD
                    ? html`<ha-voice-assistant-setup-step-wake-word
                        .hass=${this.hass}
                        .assistConfiguration=${this._assistConfiguration}
                        .assistEntityId=${assistSatelliteEntityId}
                        .deviceEntities=${this._deviceEntities(
                          this._params.deviceId,
                          this.hass.entities
                        )}
                      ></ha-voice-assistant-setup-step-wake-word>`
                    : this._step === STEP.CHANGE_WAKEWORD
                      ? html`
                          <ha-voice-assistant-setup-step-change-wake-word
                            .hass=${this.hass}
                            .assistConfiguration=${this._assistConfiguration}
                            .assistEntityId=${assistSatelliteEntityId}
                          ></ha-voice-assistant-setup-step-change-wake-word>
                        `
                      : this._step === STEP.AREA
                        ? html`
                            <ha-voice-assistant-setup-step-area
                              .hass=${this.hass}
                              .deviceId=${this._params.deviceId}
                            ></ha-voice-assistant-setup-step-area>
                          `
                        : this._step === STEP.PIPELINE
                          ? html`<ha-voice-assistant-setup-step-pipeline
                              .hass=${this.hass}
                              .languages=${this._languages}
                              .language=${this._language}
                              .assistConfiguration=${this._assistConfiguration}
                              .assistEntityId=${assistSatelliteEntityId}
                              @language-changed=${this._languageChanged}
                            ></ha-voice-assistant-setup-step-pipeline>`
                          : this._step === STEP.CLOUD
                            ? html`<ha-voice-assistant-setup-step-cloud
                                .hass=${this.hass}
                              ></ha-voice-assistant-setup-step-cloud>`
                            : this._step === STEP.LOCAL
                              ? html`<ha-voice-assistant-setup-step-local
                                  .hass=${this.hass}
                                  .language=${this._language}
                                  .localOption=${this._localOption}
                                  .assistConfiguration=${this
                                    ._assistConfiguration}
                                ></ha-voice-assistant-setup-step-local>`
                              : this._step === STEP.SUCCESS
                                ? html`<ha-voice-assistant-setup-step-success
                                    .hass=${this.hass}
                                    .assistConfiguration=${this
                                      ._assistConfiguration}
                                    .assistEntityId=${assistSatelliteEntityId}
                                    .deviceId=${this._params.deviceId}
                                  ></ha-voice-assistant-setup-step-success>`
                                : nothing}
        </div>
      </ha-dialog>
    `;
  }

  private async _getLanguages() {
    if (this._languages.length) {
      return;
    }

    const scores = await getLanguageScores(this.hass);

    this._languages = Object.entries(scores.languages)
      .filter(
        ([_lang, score]) =>
          score.cloud > 0 || score.full_local > 0 || score.focused_local > 0
      )
      .map(([lang, _score]) => lang);

    this._language =
      scores.preferred_language &&
      this._languages.includes(scores.preferred_language)
        ? scores.preferred_language
        : undefined;
  }

  private async _fetchAssistConfiguration() {
    try {
      this._assistConfiguration = await fetchAssistSatelliteConfiguration(
        this.hass,
        this._findDomainEntityId(
          this._params!.deviceId,
          this.hass.entities,
          "assist_satellite"
        )!
      );
    } catch (err: any) {
      this._error = err.message;
    }
  }

  private _handlePickLanguage(ev: HaDropdownSelectEvent) {
    this._language = ev.detail.item.value;
  }

  private _languageChanged(ev: CustomEvent) {
    if (!ev.detail.value) {
      return;
    }
    this._language = ev.detail.value;
  }

  private _goToPreviousStep() {
    if (!this._previousSteps.length) {
      return;
    }
    this._step = this._previousSteps.pop()!;
  }

  private async _goToNextStep(ev?: CustomEvent) {
    if (ev?.detail?.updateConfig) {
      await this._fetchAssistConfiguration();
    }
    if (ev?.detail?.nextStep) {
      this._nextStep = ev.detail.nextStep;
    }
    if (!ev?.detail?.noPrevious) {
      this._previousSteps.push(this._step);
    }
    if (ev?.detail?.step) {
      this._step = ev.detail.step;
      if (ev.detail.step === STEP.LOCAL) {
        this._localOption = ev.detail.option;
      }
    } else if (this._nextStep) {
      this._step = this._nextStep;
      this._nextStep = undefined;
    } else {
      this._step += 1;
    }
  }

  static get styles(): CSSResultGroup {
    return [
      haStyleDialog,
      css`
        ha-dialog {
          --dialog-content-padding: 0;
        }
        @media all and (max-width: 450px), all and (max-height: 500px) {
          .content {
            height: calc(100vh - 56px);
          }
        }
        .skip-btn {
          margin-top: 6px;
        }
        ha-alert {
          margin: 24px;
          display: block;
        }
        ha-dropdown {
          height: 48px;
          display: flex;
          align-items: center;
          margin-right: 12px;
          margin-inline-end: 12px;
          margin-inline-start: initial;
        }
      `,
    ];
  }
}

declare global {
  interface HTMLElementTagNameMap {
    "ha-voice-assistant-setup-dialog": HaVoiceAssistantSetupDialog;
  }

  interface HASSDomEvents {
    "next-step":
      | {
          step?: STEP;
          updateConfig?: boolean;
          noPrevious?: boolean;
          nextStep?: STEP;
          option?: string;
        }
      | undefined;
    "prev-step": undefined;
    "language-changed": { value: string };
  }
}
