import { mdiAlertCircle } 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 { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { fireEvent } from "../../../common/dom/fire_event";
import { computeStateName } from "../../../common/entity/compute_state_name";
import type {
  EntityDomainFilter,
  EntityDomainFilterFunc,
} from "../../../common/entity/entity_domain_filter";
import {
  generateEntityDomainFilter,
  isEmptyEntityDomainFilter,
} from "../../../common/entity/entity_domain_filter";
import "../../../components/ha-alert";
import "../../../components/ha-aliases-editor";
import "../../../components/ha-checkbox";
import "../../../components/ha-md-list-item";
import "../../../components/ha-switch";
import "../../../components/voice-assistant-brand-icon";
import { fetchCloudAlexaEntity } from "../../../data/alexa";
import type { CloudStatus, CloudStatusLoggedIn } from "../../../data/cloud";
import {
  fetchCloudStatus,
  updateCloudGoogleEntityConfig,
} from "../../../data/cloud";
import type { ExtEntityRegistryEntry } from "../../../data/entity/entity_registry";
import {
  getExtendedEntityRegistryEntry,
  updateEntityRegistryEntry,
} from "../../../data/entity/entity_registry";
import type { ExposeEntitySettings } from "../../../data/expose";
import { exposeEntities, voiceAssistants } from "../../../data/expose";
import type { GoogleEntity } from "../../../data/google_assistant";
import { fetchCloudGoogleEntity } from "../../../data/google_assistant";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import { haStyle } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types";
import { documentationUrl } from "../../../util/documentation-url";
import type { EntityRegistrySettings } from "../entities/entity-registry-settings";

@customElement("entity-voice-settings")
export class EntityVoiceSettings extends SubscribeMixin(LitElement) {
  @property({ attribute: false }) public hass!: HomeAssistant;

  @property({ attribute: false }) public entityId!: string;

  @property({ attribute: false }) public exposed!: ExposeEntitySettings;

  @property({ attribute: false }) public entry?: ExtEntityRegistryEntry;

  @state() private _cloudStatus?: CloudStatus;

  @state() private _aliases?: (string | null)[];

  @state() private _googleEntity?: GoogleEntity;

  @state() private _unsupported: Partial<
    Record<"cloud.google_assistant" | "cloud.alexa" | "conversation", boolean>
  > = {};

  protected willUpdate(changedProps: PropertyValues<this>) {
    if (!isComponentLoaded(this.hass.config, "cloud")) {
      return;
    }
    if (changedProps.has("entityId") && this.entityId) {
      this._fetchEntities();
    }
    if (!this.hasUpdated) {
      fetchCloudStatus(this.hass).then((status) => {
        this._cloudStatus = status;
      });
    }
  }

  private async _fetchEntities() {
    try {
      const googleEntity = await fetchCloudGoogleEntity(
        this.hass,
        this.entityId
      );
      this._googleEntity = googleEntity;
      this.requestUpdate("_googleEntity");
    } catch (err: any) {
      if (err.code === "not_supported") {
        this._unsupported["cloud.google_assistant"] = true;
        this.requestUpdate("_unsupported");
      }
    }

    try {
      await fetchCloudAlexaEntity(this.hass, this.entityId);
    } catch (err: any) {
      if (err.code === "not_supported") {
        this._unsupported["cloud.alexa"] = true;
        this.requestUpdate("_unsupported");
      }
    }
  }

  private _getEntityFilterFuncs = memoizeOne(
    (googleFilter: EntityDomainFilter, alexaFilter: EntityDomainFilter) => ({
      google: generateEntityDomainFilter(
        googleFilter.include_domains,
        googleFilter.include_entities,
        googleFilter.exclude_domains,
        googleFilter.exclude_entities
      ),
      alexa: generateEntityDomainFilter(
        alexaFilter.include_domains,
        alexaFilter.include_entities,
        alexaFilter.exclude_domains,
        alexaFilter.exclude_entities
      ),
    })
  );

  protected render() {
    const googleEnabled =
      this._cloudStatus?.logged_in === true &&
      this._cloudStatus.prefs.google_enabled === true;

    const alexaEnabled =
      this._cloudStatus?.logged_in === true &&
      this._cloudStatus.prefs.alexa_enabled === true;

    const showAssistants = [...Object.keys(voiceAssistants)];
    const uiAssistants = [...showAssistants];

    const alexaManual =
      alexaEnabled &&
      !isEmptyEntityDomainFilter(
        (this._cloudStatus as CloudStatusLoggedIn).alexa_entities
      );
    const googleManual =
      googleEnabled &&
      !isEmptyEntityDomainFilter(
        (this._cloudStatus as CloudStatusLoggedIn).google_entities
      );

    if (!googleEnabled) {
      showAssistants.splice(
        showAssistants.indexOf("cloud.google_assistant"),
        1
      );
      uiAssistants.splice(showAssistants.indexOf("cloud.google_assistant"), 1);
    } else if (googleManual) {
      uiAssistants.splice(uiAssistants.indexOf("cloud.google_assistant"), 1);
    }

    if (!alexaEnabled) {
      showAssistants.splice(showAssistants.indexOf("cloud.alexa"), 1);
      uiAssistants.splice(uiAssistants.indexOf("cloud.alexa"), 1);
    } else if (alexaManual) {
      uiAssistants.splice(uiAssistants.indexOf("cloud.alexa"), 1);
    }

    const uiExposed = uiAssistants.some((key) => this.exposed[key]);

    let manFilterFuncs:
      | {
          google: EntityDomainFilterFunc;
          alexa: EntityDomainFilterFunc;
        }
      | undefined;

    if (alexaManual || googleManual) {
      manFilterFuncs = this._getEntityFilterFuncs(
        (this._cloudStatus as CloudStatusLoggedIn).google_entities,
        (this._cloudStatus as CloudStatusLoggedIn).alexa_entities
      );
    }

    const manExposedAlexa = alexaManual && manFilterFuncs!.alexa(this.entityId);
    const manExposedGoogle =
      googleManual && manFilterFuncs!.google(this.entityId);

    const anyExposed = uiExposed || manExposedAlexa || manExposedGoogle;

    const exposedToAlexa =
      showAssistants.includes("cloud.alexa") &&
      (alexaManual ? manExposedAlexa : this.exposed["cloud.alexa"]);
    const exposedToGoogle =
      showAssistants.includes("cloud.google_assistant") &&
      (googleManual
        ? manExposedGoogle
        : this.exposed["cloud.google_assistant"]);
    const exposedToAssist = this.exposed.conversation;

    return html`
      <ha-md-list-item>
        <h3 slot="headline">
          ${this.hass.localize("ui.dialogs.voice-settings.expose_header")}
        </h3>
        <ha-switch
          slot="end"
          @change=${this._toggleAll}
          .assistants=${uiAssistants}
          .checked=${anyExposed}
        ></ha-switch>
      </ha-md-list-item>
      ${
        anyExposed
          ? showAssistants.map((key) => {
              const supported = !this._unsupported[key];

              const exposed =
                alexaManual && key === "cloud.alexa"
                  ? manExposedAlexa
                  : googleManual && key === "cloud.google_assistant"
                    ? manExposedGoogle
                    : this.exposed[key];

              const manualConfig =
                (alexaManual && key === "cloud.alexa") ||
                (googleManual && key === "cloud.google_assistant");

              const support2fa =
                key === "cloud.google_assistant" &&
                !googleManual &&
                supported &&
                this._googleEntity?.might_2fa;

              return html`
                <ha-md-list-item>
                  <voice-assistant-brand-icon
                    slot="start"
                    .voiceAssistantId=${key}
                  >
                  </voice-assistant-brand-icon>
                  <span slot="headline">${voiceAssistants[key].name}</span>
                  ${
                    !supported
                      ? html`<div slot="supporting-text" class="unsupported">
                          <ha-svg-icon .path=${mdiAlertCircle}></ha-svg-icon>
                          ${this.hass.localize(
                            "ui.dialogs.voice-settings.unsupported"
                          )}
                        </div>`
                      : nothing
                  }
                  ${
                    manualConfig
                      ? html`
                          <div slot="supporting-text">
                            ${this.hass.localize(
                              "ui.dialogs.voice-settings.manual_config"
                            )}
                          </div>
                        `
                      : nothing
                  }
                  ${
                    support2fa
                      ? html`
                          <ha-checkbox
                            slot="supporting-text"
                            .checked=${!this._googleEntity!.disable_2fa}
                            @change=${this._2faChanged}
                          >
                            ${this.hass.localize(
                              "ui.dialogs.voice-settings.ask_pin"
                            )}
                          </ha-checkbox>
                        `
                      : nothing
                  }
                  <ha-switch
                    slot="end"
                    .assistant=${key}
                    @change=${this._toggleAssistant}
                    .disabled=${manualConfig || (!exposed && !supported)}
                    .checked=${exposed}
                  ></ha-switch>
                </ha-md-list-item>
              `;
            })
          : nothing
      }

      <h3 class="header">
        ${this.hass.localize("ui.dialogs.voice-settings.aliases_header")}
      </h3>

      <p class="description">
        ${[
          this.hass.localize("ui.dialogs.voice-settings.aliases_description"),
          exposedToAlexa &&
            this.hass.localize(
              "ui.dialogs.voice-settings.aliases_description_alexa"
            ),
          exposedToGoogle &&
            this.hass.localize(
              "ui.dialogs.voice-settings.aliases_description_google"
            ),
          exposedToAssist &&
            (exposedToAlexa || exposedToGoogle) &&
            this.hass.localize(
              "ui.dialogs.voice-settings.aliases_description_assist"
            ),
        ]
          .filter(Boolean)
          .join(" ")}
      </p>

      ${
        !this.entry
          ? html`<ha-alert alert-type="warning">
              ${this.hass.localize(
                "ui.dialogs.voice-settings.aliases_no_unique_id",
                {
                  faq_link: html`<a
                    href=${documentationUrl(this.hass, "/faq/unique_id")}
                    target="_blank"
                    rel="noreferrer"
                    >${this.hass.localize("ui.dialogs.entity_registry.faq")}</a
                  >`,
                }
              )}
            </ha-alert>`
          : html`
              <ha-md-list-item>
                <span slot="headline">
                  ${
                    this.hass.states[this.entityId]
                      ? computeStateName(this.hass.states[this.entityId])
                      : this.entityId
                  }
                </span>
                <span slot="supporting-text">
                  ${this.hass.localize(
                    "ui.dialogs.voice-settings.entity_name_alias_description"
                  )}
                </span>
                <ha-switch
                  slot="end"
                  .checked=${(this._aliases ?? this.entry.aliases).includes(null)}
                  @change=${this._toggleEntityNameAlias}
                ></ha-switch>
              </ha-md-list-item>
              <ha-aliases-editor
                .aliases=${(this._aliases ?? this.entry.aliases).filter(
                  (a): a is string => a !== null
                )}
                sortable
                @value-changed=${this._aliasesChanged}
              ></ha-aliases-editor>
            `
      }
    `;
  }

  private async _toggleEntityNameAlias(ev) {
    const enabled = ev.target.checked;
    const currentAliases = this._aliases ?? this.entry?.aliases ?? [];
    if (enabled) {
      this._aliases = [null, ...currentAliases.filter((a) => a !== null)];
    } else {
      this._aliases = currentAliases.filter((a): a is string => a !== null);
    }
    await this._saveAliases();
  }

  private _aliasesChanged(ev) {
    const currentAliases = this._aliases ?? this.entry?.aliases ?? [];
    const hasNull = currentAliases.includes(null);
    const nullAliases: (string | null)[] = hasNull ? [null] : [];
    const newStringAliases: string[] = ev.detail.value;

    this._aliases = [...nullAliases, ...newStringAliases];
    this._saveAliases();
  }

  private async _2faChanged(ev) {
    try {
      await updateCloudGoogleEntityConfig(
        this.hass,
        this.entityId,
        !ev.target.checked
      );
    } catch (_err) {
      ev.target.checked = !ev.target.checked;
    }
  }

  private async _saveAliases() {
    if (!this._aliases) {
      return;
    }
    const hasNull = this._aliases.includes(null);
    const nullAliases: null[] = hasNull ? [null] : [];
    const stringAliases = this._aliases
      .filter((a): a is string => a !== null)
      .map((alias) => alias.trim())
      .filter((alias) => alias);
    const result = await updateEntityRegistryEntry(this.hass, this.entityId, {
      aliases: [...nullAliases, ...stringAliases],
    });
    fireEvent(this, "entity-entry-updated", result.entity_entry);
  }

  private async _toggleAssistant(ev) {
    exposeEntities(
      this.hass,
      [ev.target.assistant],
      [this.entityId],
      ev.target.checked
    );
    if (this.entry) {
      const entry = await getExtendedEntityRegistryEntry(
        this.hass,
        this.entityId
      );
      fireEvent(this, "entity-entry-updated", entry);
    }
    fireEvent(this, "exposed-entities-changed");
  }

  private async _toggleAll(ev) {
    const expose = ev.target.checked;

    const assistants = expose
      ? ev.target.assistants.filter((key) => !this._unsupported[key])
      : ev.target.assistants;

    exposeEntities(this.hass, assistants, [this.entityId], ev.target.checked);
    if (this.entry) {
      const entry = await getExtendedEntityRegistryEntry(
        this.hass,
        this.entityId
      );
      fireEvent(this, "entity-entry-updated", entry);
    }
    fireEvent(this, "exposed-entities-changed");
  }

  static get styles(): CSSResultGroup {
    return [
      haStyle,
      css`
        :host {
          display: block;
          margin: 32px;
          margin-top: 0;
        }
        ha-md-list-item {
          --md-list-item-leading-space: 0;
          --md-list-item-trailing-space: 0;
          --md-item-overflow: visible;
        }
        img {
          height: 32px;
          width: 32px;
          margin-right: 16px;
          margin-inline-end: 16px;
          margin-inline-start: initial;
        }
        ha-aliases-editor {
          display: block;
        }
        ha-alert {
          display: block;
          margin-top: 16px;
        }
        .unsupported {
          display: flex;
          align-items: center;
        }
        .unsupported ha-svg-icon {
          color: var(--error-color);
          --mdc-icon-size: 16px;
          margin-right: 4px;
          margin-inline-end: 4px;
          margin-inline-start: initial;
        }
        .header {
          margin-top: 8px;
          margin-bottom: 4px;
        }
        .description {
          color: var(--secondary-text-color);
          font-size: var(--ha-font-size-m);
          line-height: var(--ha-line-height-condensed);
          margin-top: 0;
          margin-bottom: 16px;
        }
      `,
    ];
  }
}

declare global {
  interface HTMLElementTagNameMap {
    "entity-registry-settings": EntityRegistrySettings;
  }
  interface HASSDomEvents {
    "entity-entry-updated": ExtEntityRegistryEntry;
  }
}

declare global {
  interface HTMLElementTagNameMap {
    "entity-voice-settings": EntityVoiceSettings;
  }
}
