import {
  mdiAlertCircleOutline,
  mdiDevices,
  mdiPaletteSwatch,
  mdiTextureBox,
  mdiTransitConnectionVariant,
} from "@mdi/js";
import type { CSSResultGroup, PropertyValues } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import memoizeOne from "memoize-one";
import { fireEvent } from "../common/dom/fire_event";
import { caseInsensitiveStringCompare } from "../common/string/compare";
import type { Blueprints } from "../data/blueprint";
import { fetchBlueprints } from "../data/blueprint";
import type { ConfigEntry } from "../data/config_entries";
import { getConfigEntries } from "../data/config_entries";
import type { ItemType, RelatedResult } from "../data/search";
import { findRelated } from "../data/search";
import { haStyle } from "../resources/styles";
import type { HomeAssistant } from "../types";
import { brandsUrl } from "../util/brands-url";
import "./ha-icon-next";
import "./ha-list-item";
import "./ha-state-icon";
import "./ha-switch";
import "./ha-list";

@customElement("ha-related-items")
export class HaRelatedItems extends LitElement {
  @property({ attribute: false }) public hass!: HomeAssistant;

  @property({ attribute: false }) public itemType!: ItemType;

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

  @state() private _entries?: ConfigEntry[];

  @state() private _blueprints?: Record<"automation" | "script", Blueprints>;

  @state() private _related?: RelatedResult;

  protected firstUpdated(changedProps: PropertyValues<this>) {
    super.firstUpdated(changedProps);
  }

  private async _fetchConfigEntries() {
    if (this._entries) {
      return;
    }
    this.hass.loadBackendTranslation("title");
    this._entries = await getConfigEntries(this.hass);
  }

  private async _fetchBlueprints() {
    if (this._blueprints) {
      return;
    }
    const [automation, script] = await Promise.all([
      fetchBlueprints(this.hass, "automation"),
      fetchBlueprints(this.hass, "script"),
    ]);
    this._blueprints = { automation, script };
  }

  protected updated(changedProps: PropertyValues<this>) {
    super.updated(changedProps);
    if (
      (changedProps.has("itemId") || changedProps.has("itemType")) &&
      this.itemId &&
      this.itemType
    ) {
      this._findRelated();
    }
  }

  private _relatedEntities = memoizeOne((entityIds: string[]) =>
    this._toEntities(entityIds)
  );

  private _relatedAutomations = memoizeOne((automationEntityIds: string[]) =>
    this._toEntities(automationEntityIds)
  );

  private _relatedScripts = memoizeOne((scriptEntityIds: string[]) =>
    this._toEntities(scriptEntityIds)
  );

  private _relatedGroups = memoizeOne((groupEntityIds: string[]) =>
    this._toEntities(groupEntityIds)
  );

  private _relatedScenes = memoizeOne((sceneEntityIds: string[]) =>
    this._toEntities(sceneEntityIds)
  );

  private _toEntities = (entityIds: string[]) =>
    entityIds
      .map((entityId) => this.hass.states[entityId])
      .filter((entity) => entity)
      .sort((a, b) =>
        caseInsensitiveStringCompare(
          a.attributes.friendly_name ?? a.entity_id,
          b.attributes.friendly_name ?? b.entity_id,
          this.hass.language
        )
      );

  private _getConfigEntries = memoizeOne(
    (
      relatedConfigEntries: string[] | undefined,
      entries: ConfigEntry[] | undefined
    ) => {
      const configEntries =
        relatedConfigEntries && entries
          ? relatedConfigEntries.map((entryId) =>
              entries!.find((configEntry) => configEntry.entry_id === entryId)
            )
          : undefined;

      const configEntryDomains = new Set(
        configEntries?.map((entry) => entry?.domain)
      );

      return { configEntries, configEntryDomains };
    }
  );

  protected render() {
    if (!this._related) {
      return nothing;
    }
    if (Object.keys(this._related).length === 0) {
      return html`
        <ha-list>
          <ha-list-item hasMeta graphic="icon" noninteractive>
            <ha-svg-icon
              .path=${mdiAlertCircleOutline}
              slot="graphic"
            ></ha-svg-icon>
            ${this.hass.localize(
              "ui.components.related-items.no_related_found"
            )}
          </ha-list-item>
        </ha-list>
      `;
    }

    const { configEntries, configEntryDomains } = this._getConfigEntries(
      this._related.config_entry,
      this._entries
    );

    return html`
      ${this._related.entity
        ? html`
            <h3>${this.hass.localize("ui.components.related-items.entity")}</h3>
            <ha-list>
              ${this._relatedEntities(this._related.entity).map(
                (entity) => html`
                  <ha-list-item
                    @click=${this._openMoreInfo}
                    .entityId=${entity.entity_id}
                    hasMeta
                    graphic="icon"
                  >
                    <ha-state-icon
                      .stateObj=${entity}
                      slot="graphic"
                    ></ha-state-icon>
                    ${entity.attributes.friendly_name || entity.entity_id}
                    <ha-icon-next slot="meta"></ha-icon-next>
                  </ha-list-item>
                `
              )}
            </ha-list>
          `
        : nothing}
      ${this._related.device
        ? html`<h3>
              ${this.hass.localize("ui.components.related-items.device")}
            </h3>
            <ha-list>
              ${this._related.device.map((relatedDeviceId) => {
                const device = this.hass.devices[relatedDeviceId];
                if (!device) {
                  return nothing;
                }
                return html`
                  <a href="/config/devices/device/${relatedDeviceId}">
                    <ha-list-item hasMeta graphic="icon">
                      <ha-svg-icon
                        .path=${device.entry_type === "service"
                          ? mdiTransitConnectionVariant
                          : mdiDevices}
                        slot="graphic"
                      ></ha-svg-icon>
                      ${device.name_by_user || device.name}
                      <ha-icon-next slot="meta"></ha-icon-next>
                    </ha-list-item>
                  </a>
                `;
              })}
            </ha-list>`
        : nothing}
      ${configEntries || this._related.integration
        ? html`<h3>
              ${this.hass.localize("ui.components.related-items.integration")}
            </h3>
            <ha-list
              >${configEntries?.map((entry) => {
                if (!entry) {
                  return nothing;
                }
                return html`
                  <a
                    href=${`/config/integrations/integration/${entry.domain}#config_entry=${entry.entry_id}`}
                  >
                    <ha-list-item hasMeta graphic="icon">
                      <img
                        .src=${brandsUrl(
                          {
                            domain: entry.domain,
                            type: "icon",
                            darkOptimized: this.hass.themes?.darkMode,
                          },
                          this.hass.auth.data.hassUrl
                        )}
                        crossorigin="anonymous"
                        referrerpolicy="no-referrer"
                        alt=${entry.domain}
                        slot="graphic"
                      />
                      ${this.hass.localize(`component.${entry.domain}.title`)}:
                      ${entry.title} <ha-icon-next slot="meta"></ha-icon-next>
                    </ha-list-item>
                  </a>
                `;
              })}
              ${this._related.integration
                ?.filter((integration) => !configEntryDomains.has(integration))
                .map(
                  (integration) =>
                    html`<a
                      href=${`/config/integrations/integration/${integration}`}
                    >
                      <ha-list-item hasMeta graphic="icon">
                        <img
                          .src=${brandsUrl(
                            {
                              domain: integration,
                              type: "icon",
                              darkOptimized: this.hass.themes?.darkMode,
                            },
                            this.hass.auth.data.hassUrl
                          )}
                          crossorigin="anonymous"
                          referrerpolicy="no-referrer"
                          alt=${integration}
                          slot="graphic"
                        />
                        ${this.hass.localize(`component.${integration}.title`)}
                        <ha-icon-next slot="meta"></ha-icon-next>
                      </ha-list-item>
                    </a>`
                )}
            </ha-list>`
        : nothing}
      ${this._related.area
        ? html`<h3>
              ${this.hass.localize("ui.components.related-items.area")}
            </h3>
            <ha-list
              >${this._related.area.map((relatedAreaId) => {
                const area = this.hass.areas[relatedAreaId];
                if (!area) {
                  return nothing;
                }
                return html`
                  <a href="/config/areas/area/${relatedAreaId}">
                    <ha-list-item
                      hasMeta
                      .graphic=${area.picture ? "avatar" : "icon"}
                    >
                      ${area.picture
                        ? html` <div
                            class="avatar"
                            style=${styleMap({
                              backgroundImage: `url(${area.picture})`,
                            })}
                            slot="graphic"
                          ></div>`
                        : area.icon
                          ? html`<ha-icon
                              slot="graphic"
                              .icon=${area.icon}
                            ></ha-icon>`
                          : html`<ha-svg-icon
                              slot="graphic"
                              .path=${mdiTextureBox}
                            ></ha-svg-icon>`}
                      ${area.name}
                      <ha-icon-next slot="meta"></ha-icon-next>
                    </ha-list-item>
                  </a>
                `;
              })}
            </ha-list>`
        : nothing}
      ${this._related.group
        ? html`
            <h3>${this.hass.localize("ui.components.related-items.group")}</h3>
            <ha-list>
              ${this._relatedGroups(this._related.group).map(
                (group) => html`
                  <ha-list-item
                    @click=${this._openMoreInfo}
                    .entityId=${group.entity_id}
                    hasMeta
                    graphic="icon"
                  >
                    <ha-state-icon
                      .stateObj=${group}
                      slot="graphic"
                    ></ha-state-icon>
                    ${group.attributes.friendly_name || group.entity_id}
                    <ha-icon-next slot="meta"></ha-icon-next>
                  </ha-list-item>
                `
              )}
            </ha-list>
          `
        : nothing}
      ${this._related.scene
        ? html`
            <h3>${this.hass.localize("ui.components.related-items.scene")}</h3>
            <ha-list>
              ${this._relatedScenes(this._related.scene).map(
                (scene) => html`
                  <ha-list-item
                    @click=${this._openMoreInfo}
                    .entityId=${scene.entity_id}
                    hasMeta
                    graphic="icon"
                  >
                    <ha-state-icon
                      .stateObj=${scene}
                      slot="graphic"
                    ></ha-state-icon>
                    ${scene.attributes.friendly_name || scene.entity_id}
                    <ha-icon-next slot="meta"></ha-icon-next>
                  </ha-list-item>
                `
              )}
            </ha-list>
          `
        : nothing}
      ${this._related.automation_blueprint
        ? html`
            <h3>
              ${this.hass.localize("ui.components.related-items.blueprint")}
            </h3>
            <ha-list>
              ${this._related.automation_blueprint.map((path) => {
                const blueprintMeta = this._blueprints
                  ? this._blueprints.automation[path]
                  : undefined;
                return html`<a href="/config/blueprint/dashboard">
                  <ha-list-item hasMeta graphic="icon">
                    <ha-svg-icon
                      .path=${mdiPaletteSwatch}
                      slot="graphic"
                    ></ha-svg-icon>
                    ${!blueprintMeta || "error" in blueprintMeta
                      ? path
                      : blueprintMeta.metadata.name || path}
                    <ha-icon-next slot="meta"></ha-icon-next>
                  </ha-list-item>
                </a>`;
              })}
            </ha-list>
          `
        : nothing}
      ${this._related.automation
        ? html`
            <h3>
              ${this.hass.localize("ui.components.related-items.automation")}
            </h3>
            <ha-list>
              ${this._relatedAutomations(this._related.automation).map(
                (automation) => html`
                  <ha-list-item
                    @click=${this._openMoreInfo}
                    .entityId=${automation.entity_id}
                    hasMeta
                    graphic="icon"
                  >
                    <ha-state-icon
                      .stateObj=${automation}
                      slot="graphic"
                    ></ha-state-icon>
                    ${automation.attributes.friendly_name ||
                    automation.entity_id}
                    <ha-icon-next slot="meta"></ha-icon-next>
                  </ha-list-item>
                `
              )}
            </ha-list>
          `
        : nothing}
      ${this._related.script_blueprint
        ? html`
            <h3>
              ${this.hass.localize("ui.components.related-items.blueprint")}
            </h3>
            <ha-list>
              ${this._related.script_blueprint.map((path) => {
                const blueprintMeta = this._blueprints
                  ? this._blueprints.script[path]
                  : undefined;
                return html`<a href="/config/blueprint/dashboard">
                  <ha-list-item hasMeta graphic="icon">
                    <ha-svg-icon
                      .path=${mdiPaletteSwatch}
                      slot="graphic"
                    ></ha-svg-icon>
                    ${!blueprintMeta || "error" in blueprintMeta
                      ? path
                      : blueprintMeta.metadata.name || path}
                    <ha-icon-next slot="meta"></ha-icon-next>
                  </ha-list-item>
                </a>`;
              })}
            </ha-list>
          `
        : nothing}
      ${this._related.script
        ? html`
            <h3>${this.hass.localize("ui.components.related-items.script")}</h3>
            <ha-list>
              ${this._relatedScripts(this._related.script).map(
                (script) => html`
                  <ha-list-item
                    @click=${this._openMoreInfo}
                    .entityId=${script.entity_id}
                    hasMeta
                    graphic="icon"
                  >
                    <ha-state-icon
                      .stateObj=${script}
                      slot="graphic"
                    ></ha-state-icon>
                    ${script.attributes.friendly_name || script.entity_id}
                    <ha-icon-next slot="meta"></ha-icon-next>
                  </ha-list-item>
                `
              )}
            </ha-list>
          `
        : nothing}
    `;
  }

  private async _findRelated() {
    this._related = await findRelated(this.hass, this.itemType, this.itemId);
    if (this._related.config_entry) {
      this._fetchConfigEntries();
    }
    if (this._related.script_blueprint || this._related.automation_blueprint) {
      this._fetchBlueprints();
    }
  }

  private _openMoreInfo(ev: CustomEvent) {
    const entityId = (ev.target as any).entityId;
    fireEvent(this, "hass-more-info", { entityId });
  }

  static get styles(): CSSResultGroup {
    return [
      haStyle,
      css`
        a {
          color: var(--primary-color);
          text-decoration: none;
        }
        ha-list-item {
          --mdc-list-side-padding: 24px;
        }
        h3 {
          padding: 0 24px;
          margin-bottom: -8px;
        }
        h3:first-child {
          margin-top: 0;
        }
        .avatar {
          background-position: center center;
          background-size: cover;
        }
      `,
    ];
  }
}

declare global {
  interface HTMLElementTagNameMap {
    "ha-related-items": HaRelatedItems;
  }
}
