import { consume } from "@lit-labs/context";
import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
import "@material/mwc-list/mwc-list-item";
import {
  mdiCog,
  mdiDelete,
  mdiDotsVertical,
  mdiDownload,
  mdiOpenInNew,
  mdiPencil,
  mdiPlusCircle,
} from "@mdi/js";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body";
import {
  CSSResultGroup,
  LitElement,
  TemplateResult,
  css,
  html,
  nothing,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import memoizeOne from "memoize-one";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { SENSOR_ENTITIES } from "../../../common/const";
import { computeDomain } from "../../../common/entity/compute_domain";
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
import { computeStateName } from "../../../common/entity/compute_state_name";
import { stringCompare } from "../../../common/string/compare";
import { slugify } from "../../../common/string/slugify";
import { groupBy } from "../../../common/util/group-by";
import "../../../components/entity/ha-battery-icon";
import "../../../components/ha-alert";
import "../../../components/ha-button-menu";
import "../../../components/ha-icon-button";
import "../../../components/ha-icon-next";
import "../../../components/ha-svg-icon";
import { getSignedPath } from "../../../data/auth";
import {
  ConfigEntry,
  DisableConfigEntryResult,
  disableConfigEntry,
  sortConfigEntries,
} from "../../../data/config_entries";
import { fullEntitiesContext } from "../../../data/context";
import {
  DeviceRegistryEntry,
  computeDeviceName,
  removeConfigEntryFromDevice,
  updateDeviceRegistryEntry,
} from "../../../data/device_registry";
import {
  DiagnosticInfo,
  fetchDiagnosticHandler,
  getConfigEntryDiagnosticsDownloadUrl,
  getDeviceDiagnosticsDownloadUrl,
} from "../../../data/diagnostics";
import {
  EntityRegistryEntry,
  findBatteryChargingEntity,
  findBatteryEntity,
  updateEntityRegistryEntry,
} from "../../../data/entity_registry";
import { IntegrationManifest, domainToName } from "../../../data/integration";
import { SceneEntities, showSceneEditor } from "../../../data/scene";
import { RelatedResult, findRelated } from "../../../data/search";
import {
  showAlertDialog,
  showConfirmationDialog,
} from "../../../dialogs/generic/show-dialog-box";
import "../../../layouts/hass-error-screen";
import "../../../layouts/hass-subpage";
import { haStyle } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types";
import { brandsUrl } from "../../../util/brands-url";
import { fileDownload } from "../../../util/file_download";
import "../../logbook/ha-logbook";
import "../ha-config-section";
import "./device-detail/ha-device-entities-card";
import "./device-detail/ha-device-info-card";
import "./device-detail/ha-device-via-devices-card";
import { showDeviceAutomationDialog } from "./device-detail/show-dialog-device-automation";
import {
  loadDeviceRegistryDetailDialog,
  showDeviceRegistryDetailDialog,
} from "./device-registry-detail/show-dialog-device-registry-detail";

export interface EntityRegistryStateEntry extends EntityRegistryEntry {
  stateName?: string | null;
}

export interface DeviceAction {
  href?: string;
  target?: string;
  action?: (ev: any) => void;
  label: string;
  icon?: string;
  trailingIcon?: string;
  classes?: string;
}

export interface DeviceAlert {
  level: "warning" | "error" | "info";
  text: string;
}

@customElement("ha-config-device-page")
export class HaConfigDevicePage extends LitElement {
  @property({ attribute: false }) public hass!: HomeAssistant;

  @property({ attribute: false }) public entries!: ConfigEntry[];

  @property({ attribute: false }) public manifests!: IntegrationManifest[];

  @property() public deviceId!: string;

  @property({ type: Boolean, reflect: true }) public narrow = false;

  @property({ type: Boolean }) public isWide = false;

  @property({ type: Boolean }) public showAdvanced = false;

  @state() private _related?: RelatedResult;

  // If a number, it's the request ID so we make sure we don't show older info
  @state() private _diagnosticDownloadLinks?: number | DeviceAction[];

  @state() private _deleteButtons?: DeviceAction[];

  @state() private _deviceActions?: DeviceAction[];

  @state() private _deviceAlerts?: DeviceAlert[];

  @state()
  @consume({ context: fullEntitiesContext, subscribe: true })
  _entityReg!: EntityRegistryEntry[];

  private _logbookTime = { recent: 86400 };

  private _integrations = memoizeOne(
    (
      device: DeviceRegistryEntry,
      entries: ConfigEntry[],
      manifests: IntegrationManifest[]
    ): ConfigEntry[] => {
      const entryLookup: { [entryId: string]: ConfigEntry } = {};
      for (const entry of entries) {
        entryLookup[entry.entry_id] = entry;
      }
      const manifestLookup: { [domain: string]: IntegrationManifest } = {};
      for (const manifest of manifests) {
        manifestLookup[manifest.domain] = manifest;
      }
      const deviceEntries = device.config_entries
        .filter((entId) => entId in entryLookup)
        .map((entry) => entryLookup[entry]);

      return sortConfigEntries(deviceEntries, manifestLookup);
    }
  );

  private _entities = memoizeOne(
    (
      deviceId: string,
      entities: EntityRegistryEntry[]
    ): EntityRegistryStateEntry[] =>
      entities
        .filter((entity) => entity.device_id === deviceId)
        .map((entity) => ({
          ...entity,
          stateName: this._computeEntityName(entity),
        }))
        .sort((ent1, ent2) =>
          stringCompare(
            ent1.stateName || `zzz${ent1.entity_id}`,
            ent2.stateName || `zzz${ent2.entity_id}`,
            this.hass.locale.language
          )
        )
  );

  private _deviceIdInList = memoizeOne((deviceId: string) => [deviceId]);

  private _entityIds = memoizeOne(
    (entries: EntityRegistryStateEntry[]): string[] =>
      entries.map((entry) => entry.entity_id)
  );

  private _entitiesByCategory = memoizeOne(
    (entities: EntityRegistryEntry[]) => {
      const result = groupBy(entities, (entry) =>
        entry.entity_category
          ? entry.entity_category
          : computeDomain(entry.entity_id) === "event"
            ? "event"
            : SENSOR_ENTITIES.includes(computeDomain(entry.entity_id))
              ? "sensor"
              : "control"
      ) as Record<
        | "control"
        | "event"
        | "sensor"
        | NonNullable<EntityRegistryEntry["entity_category"]>,
        EntityRegistryStateEntry[]
      >;
      for (const key of [
        "config",
        "control",
        "diagnostic",
        "event",
        "sensor",
      ]) {
        if (!(key in result)) {
          result[key] = [];
        }
      }

      return result;
    }
  );

  private _batteryEntity = memoizeOne(
    (entities: EntityRegistryEntry[]): EntityRegistryEntry | undefined =>
      findBatteryEntity(this.hass, entities)
  );

  private _batteryChargingEntity = memoizeOne(
    (entities: EntityRegistryEntry[]): EntityRegistryEntry | undefined =>
      findBatteryChargingEntity(this.hass, entities)
  );

  public willUpdate(changedProps) {
    super.willUpdate(changedProps);

    if (
      changedProps.has("deviceId") ||
      changedProps.has("devices") ||
      changedProps.has("entries")
    ) {
      this._diagnosticDownloadLinks = undefined;
      this._deleteButtons = undefined;
      this._deviceActions = undefined;
      this._deviceAlerts = undefined;
    }

    if (
      (this._diagnosticDownloadLinks &&
        this._deleteButtons &&
        this._deviceActions &&
        this._deviceAlerts) ||
      !this.deviceId ||
      !this.entries
    ) {
      return;
    }

    this._diagnosticDownloadLinks = Math.random();
    this._deleteButtons = []; // To prevent re-rendering if no delete buttons
    this._deviceActions = [];
    this._deviceAlerts = [];
    this._getDiagnosticButtons(this._diagnosticDownloadLinks);
    this._getDeleteActions();
    this._getDeviceActions();
    this._getDeviceAlerts();
  }

  protected firstUpdated(changedProps) {
    super.firstUpdated(changedProps);
    loadDeviceRegistryDetailDialog();
  }

  protected updated(changedProps) {
    super.updated(changedProps);
    if (changedProps.has("deviceId")) {
      this._findRelated();
    }
  }

  protected render() {
    if (!this.hass || !this.deviceId) {
      return nothing;
    }
    const device = this.hass.devices[this.deviceId];

    if (!device) {
      return html`
        <hass-error-screen
          .hass=${this.hass}
          .error=${this.hass.localize(
            "ui.panel.config.devices.device_not_found"
          )}
        ></hass-error-screen>
      `;
    }

    const deviceName = computeDeviceName(device, this.hass);
    const integrations = this._integrations(
      device,
      this.entries,
      this.manifests
    );
    const entities = this._entities(this.deviceId, this._entityReg);
    const entitiesByCategory = this._entitiesByCategory(entities);
    const batteryEntity = this._batteryEntity(entities);
    const batteryChargingEntity = this._batteryChargingEntity(entities);
    const battery = batteryEntity
      ? this.hass.states[batteryEntity.entity_id]
      : undefined;
    const batteryDomain = battery ? computeStateDomain(battery) : undefined;

    const batteryChargingState = batteryChargingEntity
      ? this.hass.states[batteryChargingEntity.entity_id]
      : undefined;
    const area = device.area_id ? this.hass.areas[device.area_id] : undefined;

    const deviceInfo: TemplateResult[] = integrations.map(
      (integration) =>
        html`<a
          slot="actions"
          href=${`/config/integrations/integration/${integration.domain}#config_entry=${integration.entry_id}`}
        >
          <ha-list-item graphic="icon" hasMeta>
            <img
              slot="graphic"
              alt=${domainToName(this.hass.localize, integration.domain)}
              src=${brandsUrl({
                domain: integration.domain,
                type: "icon",
                darkOptimized: this.hass.themes?.darkMode,
              })}
              crossorigin="anonymous"
              referrerpolicy="no-referrer"
            />

            ${domainToName(this.hass.localize, integration.domain)}
            <ha-icon-next slot="meta"></ha-icon-next>
          </ha-list-item>
        </a>`
    );

    const actions = [...(this._deviceActions || [])];
    if (Array.isArray(this._diagnosticDownloadLinks)) {
      actions.push(...this._diagnosticDownloadLinks);
    }
    if (this._deleteButtons) {
      actions.push(...this._deleteButtons);
    }

    // Move all warning actions to the end
    actions.sort((a, b) => {
      if (a.classes === "warning" && b.classes !== "warning") {
        return 1;
      }
      if (a.classes !== "warning" && b.classes === "warning") {
        return -1;
      }
      return 0;
    });

    const firstDeviceAction = actions.shift();

    if (device.disabled_by) {
      deviceInfo.push(html`
        <ha-alert alert-type="warning">
          ${this.hass.localize("ui.panel.config.devices.enabled_cause", {
            type: this.hass.localize(
              `ui.panel.config.devices.type.${device.entry_type || "device"}`
            ),
            cause: this.hass.localize(
              `ui.panel.config.devices.disabled_by.${device.disabled_by}`
            ),
          })}
        </ha-alert>
        ${device.disabled_by === "user"
          ? html`
              <div class="card-actions" slot="actions">
                <mwc-button unelevated @click=${this._enableDevice}>
                  ${this.hass.localize("ui.common.enable")}
                </mwc-button>
              </div>
            `
          : ""}
      `);
    }

    this._renderIntegrationInfo(device, integrations, deviceInfo);

    const automationCard = isComponentLoaded(this.hass, "automation")
      ? html`
          <ha-card outlined>
            <h1 class="card-header">
              ${this.hass.localize(
                "ui.panel.config.devices.automation.automations_heading"
              )}
              <ha-icon-button
                @click=${this._showAutomationDialog}
                .disabled=${device.disabled_by}
                .label=${device.disabled_by
                  ? this.hass.localize(
                      "ui.panel.config.devices.automation.create_disable",
                      {
                        type: this.hass.localize(
                          `ui.panel.config.devices.type.${
                            device.entry_type || "device"
                          }`
                        ),
                      }
                    )
                  : this.hass.localize(
                      "ui.panel.config.devices.automation.create",
                      {
                        type: this.hass.localize(
                          `ui.panel.config.devices.type.${
                            device.entry_type || "device"
                          }`
                        ),
                      }
                    )}
                .path=${mdiPlusCircle}
              ></ha-icon-button>
            </h1>
            ${this._related?.automation?.length
              ? html`
                  <div class="items">
                    ${this._related.automation.map((automation) => {
                      const entityState = this.hass.states[automation];
                      return entityState
                        ? html`<div>
                            <a
                              href=${ifDefined(
                                entityState.attributes.id
                                  ? `/config/automation/edit/${encodeURIComponent(entityState.attributes.id)}`
                                  : undefined
                              )}
                            >
                              <ha-list-item
                                hasMeta
                                .automation=${entityState}
                                .disabled=${!entityState.attributes.id}
                              >
                                ${computeStateName(entityState)}
                                <ha-icon-next slot="meta"></ha-icon-next>
                              </ha-list-item>
                            </a>
                            ${!entityState.attributes.id
                              ? html`
                                  <simple-tooltip animation-delay="0">
                                    ${this.hass.localize(
                                      "ui.panel.config.devices.cant_edit"
                                    )}
                                  </simple-tooltip>
                                `
                              : ""}
                          </div> `
                        : "";
                    })}
                  </div>
                `
              : html`
                  <div class="card-content">
                    ${this.hass.localize("ui.panel.config.devices.add_prompt", {
                      name: this.hass.localize(
                        "ui.panel.config.devices.automation.automations"
                      ),
                      type: this.hass.localize(
                        `ui.panel.config.devices.type.${
                          device.entry_type || "device"
                        }`
                      ),
                    })}
                  </div>
                `}
          </ha-card>
        `
      : "";

    const sceneCard =
      isComponentLoaded(this.hass, "scene") && entities.length
        ? html`
            <ha-card outlined>
              <h1 class="card-header">
                ${this.hass.localize(
                  "ui.panel.config.devices.scene.scenes_heading"
                )}

                <ha-icon-button
                  @click=${this._createScene}
                  .disabled=${device.disabled_by}
                  .label=${device.disabled_by
                    ? this.hass.localize(
                        "ui.panel.config.devices.scene.create_disable",
                        {
                          type: this.hass.localize(
                            `ui.panel.config.devices.type.${
                              device.entry_type || "device"
                            }`
                          ),
                        }
                      )
                    : this.hass.localize(
                        "ui.panel.config.devices.scene.create",
                        {
                          type: this.hass.localize(
                            `ui.panel.config.devices.type.${
                              device.entry_type || "device"
                            }`
                          ),
                        }
                      )}
                  .path=${mdiPlusCircle}
                ></ha-icon-button>
              </h1>
              ${this._related?.scene?.length
                ? html`
                    <div class="items">
                      ${this._related.scene.map((scene) => {
                        const entityState = this.hass.states[scene];
                        return entityState
                          ? html`
                              <div>
                                <a
                                  href=${ifDefined(
                                    entityState.attributes.id
                                      ? `/config/scene/edit/${entityState.attributes.id}`
                                      : undefined
                                  )}
                                >
                                  <ha-list-item
                                    hasMeta
                                    .scene=${entityState}
                                    .disabled=${!entityState.attributes.id}
                                  >
                                    ${computeStateName(entityState)}
                                    <ha-icon-next slot="meta"></ha-icon-next>
                                  </ha-list-item>
                                </a>
                                ${!entityState.attributes.id
                                  ? html`
                                      <simple-tooltip animation-delay="0">
                                        ${this.hass.localize(
                                          "ui.panel.config.devices.cant_edit"
                                        )}
                                      </simple-tooltip>
                                    `
                                  : ""}
                              </div>
                            `
                          : "";
                      })}
                    </div>
                  `
                : html`
                    <div class="card-content">
                      ${this.hass.localize(
                        "ui.panel.config.devices.add_prompt",
                        {
                          name: this.hass.localize(
                            "ui.panel.config.devices.scene.scenes"
                          ),
                          type: this.hass.localize(
                            `ui.panel.config.devices.type.${
                              device.entry_type || "device"
                            }`
                          ),
                        }
                      )}
                    </div>
                  `}
            </ha-card>
          `
        : "";

    const scriptCard = isComponentLoaded(this.hass, "script")
      ? html`
          <ha-card outlined>
            <h1 class="card-header">
              ${this.hass.localize(
                "ui.panel.config.devices.script.scripts_heading"
              )}
              <ha-icon-button
                @click=${this._showScriptDialog}
                .disabled=${device.disabled_by}
                .label=${device.disabled_by
                  ? this.hass.localize(
                      "ui.panel.config.devices.script.create_disable",
                      {
                        type: this.hass.localize(
                          `ui.panel.config.devices.type.${
                            device.entry_type || "device"
                          }`
                        ),
                      }
                    )
                  : this.hass.localize(
                      "ui.panel.config.devices.script.create",
                      {
                        type: this.hass.localize(
                          `ui.panel.config.devices.type.${
                            device.entry_type || "device"
                          }`
                        ),
                      }
                    )}
                .path=${mdiPlusCircle}
              ></ha-icon-button>
            </h1>
            ${this._related?.script?.length
              ? html`
                  <div class="items">
                    ${this._related.script.map((script) => {
                      const entityState = this.hass.states[script];
                      const entry = this._entityReg.find(
                        (e) => e.entity_id === script
                      );
                      let url = `/config/script/show/${entityState.entity_id}`;
                      if (entry) {
                        url = `/config/script/edit/${entry.unique_id}`;
                      }
                      return entityState
                        ? html`
                            <a href=${url}>
                              <ha-list-item hasMeta .script=${script}>
                                ${computeStateName(entityState)}
                                <ha-icon-next slot="meta"></ha-icon-next>
                              </ha-list-item>
                            </a>
                          `
                        : "";
                    })}
                  </div>
                `
              : html`
                  <div class="card-content">
                    ${this.hass.localize("ui.panel.config.devices.add_prompt", {
                      name: this.hass.localize(
                        "ui.panel.config.devices.script.scripts"
                      ),
                      type: this.hass.localize(
                        `ui.panel.config.devices.type.${
                          device.entry_type || "device"
                        }`
                      ),
                    })}
                  </div>
                `}
          </ha-card>
        `
      : "";

    return html`
      <hass-subpage
        .hass=${this.hass}
        .narrow=${this.narrow}
        .header=${deviceName}
      >

                <ha-icon-button
                  slot="toolbar-icon"
                  .path=${mdiPencil}
                  @click=${this._showSettings}
                  .label=${this.hass.localize(
                    "ui.panel.config.devices.edit_settings"
                  )}
                ></ha-icon-button>
        <div class="container">
          <div class="header fullwidth">
            ${
              area
                ? html`<div class="header-name">
                    <a href="/config/areas/area/${area.area_id}"
                      >${this.hass.localize(
                        "ui.panel.config.integrations.config_entry.area",
                        { area: area.name || "Unnamed Area" }
                      )}</a
                    >
                  </div>`
                : ""
            }
                <div class="header-right">
                  ${
                    battery &&
                    (batteryDomain === "binary_sensor" ||
                      !isNaN(battery.state as any))
                      ? html`
                          <div class="battery">
                            ${batteryDomain === "sensor"
                              ? this.hass.formatEntityState(battery)
                              : nothing}
                            <ha-battery-icon
                              .hass=${this.hass}
                              .batteryStateObj=${battery}
                              .batteryChargingStateObj=${batteryChargingState}
                            ></ha-battery-icon>
                          </div>
                        `
                      : ""
                  }
                  ${
                    integrations.length
                      ? html`
                          <img
                            alt=${domainToName(
                              this.hass.localize,
                              integrations[0].domain
                            )}
                            src=${brandsUrl({
                              domain: integrations[0].domain,
                              type: "logo",
                              darkOptimized: this.hass.themes?.darkMode,
                            })}
                            crossorigin="anonymous"
                            referrerpolicy="no-referrer"
                            @load=${this._onImageLoad}
                            @error=${this._onImageError}
                          />
                        `
                      : ""
                  }

                </div>
          </div>
          <div class="column">
              ${
                this._deviceAlerts?.length
                  ? html`
                      <div>
                        ${this._deviceAlerts.map(
                          (alert) => html`
                            <ha-alert .alertType=${alert.level}>
                              ${alert.text}
                            </ha-alert>
                          `
                        )}
                      </div>
                    `
                  : ""
              }
              <ha-device-info-card
                .hass=${this.hass}
                .device=${device}
              >
                ${deviceInfo}
                ${
                  firstDeviceAction || actions.length
                    ? html`
                        <div class="card-actions" slot="actions">
                          <div>
                            <a
                              href=${ifDefined(firstDeviceAction!.href)}
                              rel=${ifDefined(
                                firstDeviceAction!.target
                                  ? "noreferrer"
                                  : undefined
                              )}
                              target=${ifDefined(firstDeviceAction!.target)}
                            >
                              <mwc-button
                                class=${ifDefined(firstDeviceAction!.classes)}
                                .action=${firstDeviceAction!.action}
                                @click=${this._deviceActionClicked}
                                graphic="icon"
                              >
                                ${firstDeviceAction!.label}
                                ${firstDeviceAction!.icon
                                  ? html`
                                      <ha-svg-icon
                                        class=${ifDefined(
                                          firstDeviceAction!.classes
                                        )}
                                        .path=${firstDeviceAction!.icon}
                                        slot="graphic"
                                      ></ha-svg-icon>
                                    `
                                  : ""}
                                ${firstDeviceAction!.trailingIcon
                                  ? html`
                                      <ha-svg-icon
                                        .path=${firstDeviceAction!.trailingIcon}
                                        slot="trailingIcon"
                                      ></ha-svg-icon>
                                    `
                                  : ""}
                              </mwc-button>
                            </a>
                          </div>

                          ${actions.length
                            ? html`
                                <ha-button-menu>
                                  <ha-icon-button
                                    slot="trigger"
                                    .label=${this.hass.localize(
                                      "ui.common.menu"
                                    )}
                                    .path=${mdiDotsVertical}
                                  ></ha-icon-button>
                                  ${actions.map((deviceAction) => {
                                    const listItem = html`<mwc-list-item
                                      class=${ifDefined(deviceAction.classes)}
                                      .action=${deviceAction.action}
                                      @click=${this._deviceActionClicked}
                                      graphic="icon"
                                      .hasMeta=${Boolean(
                                        deviceAction.trailingIcon
                                      )}
                                    >
                                      ${deviceAction.label}
                                      ${deviceAction.icon
                                        ? html`
                                            <ha-svg-icon
                                              class=${ifDefined(
                                                deviceAction.classes
                                              )}
                                              .path=${deviceAction.icon}
                                              slot="graphic"
                                            ></ha-svg-icon>
                                          `
                                        : ""}
                                      ${deviceAction.trailingIcon
                                        ? html`
                                            <ha-svg-icon
                                              slot="meta"
                                              .path=${deviceAction.trailingIcon}
                                            ></ha-svg-icon>
                                          `
                                        : ""}
                                    </mwc-list-item>`;
                                    return deviceAction.href
                                      ? html`<a
                                          href=${deviceAction.href}
                                          target=${ifDefined(
                                            deviceAction.target
                                          )}
                                          rel=${ifDefined(
                                            deviceAction.target
                                              ? "noreferrer"
                                              : undefined
                                          )}
                                          >${listItem}
                                        </a>`
                                      : listItem;
                                  })}
                                </ha-button-menu>
                              `
                            : ""}
                        </div>
                      `
                    : ""
                }
              </ha-device-info-card>
            ${!this.narrow ? [automationCard, sceneCard, scriptCard] : ""}
          </div>
          <div class="column">
            ${(
              ["control", "sensor", "event", "config", "diagnostic"] as const
            ).map((category) =>
              // Make sure we render controls if no other cards will be rendered
              entitiesByCategory[category].length > 0 ||
              (entities.length === 0 && category === "control")
                ? html`
                    <ha-device-entities-card
                      .hass=${this.hass}
                      .header=${this.hass.localize(
                        `ui.panel.config.devices.entities.${category}`
                      )}
                      .deviceName=${deviceName}
                      .entities=${entitiesByCategory[category]}
                      .showHidden=${device.disabled_by !== null}
                    >
                    </ha-device-entities-card>
                  `
                : ""
            )}
            <ha-device-via-devices-card
              .hass=${this.hass}
              .deviceId=${this.deviceId}
            ></ha-device-via-devices-card>
          </div>
          <div class="column">
            ${this.narrow ? [automationCard, sceneCard, scriptCard] : ""}
            ${
              isComponentLoaded(this.hass, "logbook")
                ? html`
                    <ha-card outlined>
                      <h1 class="card-header">
                        ${this.hass.localize("panel.logbook")}
                      </h1>
                      <ha-logbook
                        .hass=${this.hass}
                        .time=${this._logbookTime}
                        .entityIds=${this._entityIds(entities)}
                        .deviceIds=${this._deviceIdInList(this.deviceId)}
                        virtualize
                        narrow
                        no-icon
                      ></ha-logbook>
                    </ha-card>
                  `
                : ""
            }
            </div>
          </div>
        </ha-config-section>
      </hass-subpage>    `;
  }

  private async _getDiagnosticButtons(requestId: number): Promise<void> {
    if (!isComponentLoaded(this.hass, "diagnostics")) {
      return;
    }

    const device = this.hass.devices[this.deviceId];

    if (!device) {
      return;
    }

    let links = await Promise.all(
      this._integrations(device, this.entries, this.manifests).map(
        async (entry): Promise<boolean | { link: string; domain: string }> => {
          if (entry.state !== "loaded") {
            return false;
          }
          let info: DiagnosticInfo;
          try {
            info = await fetchDiagnosticHandler(this.hass, entry.domain);
          } catch (err: any) {
            if (err.code === "not_found") {
              return false;
            }
            throw err;
          }

          if (!info.handlers.device && !info.handlers.config_entry) {
            return false;
          }
          return {
            link: info.handlers.device
              ? getDeviceDiagnosticsDownloadUrl(entry.entry_id, this.deviceId)
              : getConfigEntryDiagnosticsDownloadUrl(entry.entry_id),
            domain: entry.domain,
          };
        }
      )
    );

    links = links.filter(Boolean);

    if (this._diagnosticDownloadLinks !== requestId) {
      return;
    }
    if (links.length > 0) {
      this._diagnosticDownloadLinks = (
        links as { link: string; domain: string }[]
      ).map((link) => ({
        href: link.link,
        icon: mdiDownload,
        action: (ev) => this._signUrl(ev),
        label:
          links.length > 1
            ? this.hass.localize(
                `ui.panel.config.devices.download_diagnostics_integration`,
                {
                  integration: domainToName(this.hass.localize, link.domain),
                }
              )
            : this.hass.localize(
                `ui.panel.config.devices.download_diagnostics`
              ),
      }));
    }
  }

  private _getDeleteActions() {
    const device = this.hass.devices[this.deviceId];

    if (!device) {
      return;
    }

    const buttons: DeviceAction[] = [];
    this._integrations(device, this.entries, this.manifests).forEach(
      (entry) => {
        if (entry.state !== "loaded" || !entry.supports_remove_device) {
          return;
        }
        buttons.push({
          action: async () => {
            const confirmed = await showConfirmationDialog(this, {
              text:
                this._integrations(device, this.entries, this.manifests)
                  .length > 1
                  ? this.hass.localize(
                      `ui.panel.config.devices.confirm_delete_integration`,
                      {
                        integration: domainToName(
                          this.hass.localize,
                          entry.domain
                        ),
                      }
                    )
                  : this.hass.localize(
                      `ui.panel.config.devices.confirm_delete`
                    ),
            });

            if (!confirmed) {
              return;
            }

            await removeConfigEntryFromDevice(
              this.hass!,
              this.deviceId,
              entry.entry_id
            );
          },
          classes: "warning",
          icon: mdiDelete,
          label:
            this._integrations(device, this.entries, this.manifests).length > 1
              ? this.hass.localize(
                  `ui.panel.config.devices.delete_device_integration`,
                  {
                    integration: domainToName(this.hass.localize, entry.domain),
                  }
                )
              : this.hass.localize(`ui.panel.config.devices.delete_device`),
        });
      }
    );

    if (buttons.length > 0) {
      this._deleteButtons = buttons;
    }
  }

  private async _getDeviceActions() {
    const device = this.hass.devices[this.deviceId];

    if (!device) {
      return;
    }

    const deviceActions: DeviceAction[] = [];

    const configurationUrlIsHomeAssistant =
      device.configuration_url?.startsWith("homeassistant://") || false;

    const configurationUrl = configurationUrlIsHomeAssistant
      ? device.configuration_url!.replace("homeassistant://", "/")
      : device.configuration_url;

    if (configurationUrl) {
      deviceActions.push({
        href: configurationUrl,
        target: configurationUrlIsHomeAssistant ? undefined : "_blank",
        icon: mdiCog,
        label: this.hass.localize(
          "ui.panel.config.devices.open_configuration_url"
        ),
        trailingIcon: mdiOpenInNew,
      });
    }

    const domains = this._integrations(
      device,
      this.entries,
      this.manifests
    ).map((int) => int.domain);

    if (domains.includes("mqtt")) {
      const mqtt = await import(
        "./device-detail/integration-elements/mqtt/device-actions"
      );
      const actions = mqtt.getMQTTDeviceActions(this, device);
      deviceActions.push(...actions);
    }
    if (domains.includes("zha")) {
      const zha = await import(
        "./device-detail/integration-elements/zha/device-actions"
      );
      const actions = await zha.getZHADeviceActions(this, this.hass, device);
      deviceActions.push(...actions);
    }
    if (domains.includes("zwave_js")) {
      const zwave = await import(
        "./device-detail/integration-elements/zwave_js/device-actions"
      );
      const actions = await zwave.getZwaveDeviceActions(
        this,
        this.hass,
        device
      );
      deviceActions.push(...actions);
    }
    if (domains.includes("matter")) {
      const matter = await import(
        "./device-detail/integration-elements/matter/device-actions"
      );
      const actions = await matter.getMatterDeviceActions(
        this,
        this.hass,
        device
      );
      deviceActions.push(...actions);
    }

    this._deviceActions = deviceActions;
  }

  private async _getDeviceAlerts() {
    const device = this.hass.devices[this.deviceId];

    if (!device) {
      return;
    }

    const deviceAlerts: DeviceAlert[] = [];

    const domains = this._integrations(
      device,
      this.entries,
      this.manifests
    ).map((int) => int.domain);

    if (domains.includes("zwave_js")) {
      const zwave = await import(
        "./device-detail/integration-elements/zwave_js/device-alerts"
      );

      const alerts = await zwave.getZwaveDeviceAlerts(this.hass, device);
      deviceAlerts.push(...alerts);
    }

    if (deviceAlerts.length) {
      this._deviceAlerts = deviceAlerts;
    }
  }

  private _computeEntityName(entity: EntityRegistryEntry) {
    if (entity.name) {
      return entity.name;
    }
    const entityState = this.hass.states[entity.entity_id];
    return entityState ? computeStateName(entityState) : null;
  }

  private _onImageLoad(ev) {
    ev.target.style.display = "inline-block";
  }

  private _onImageError(ev) {
    ev.target.style.display = "none";
  }

  private async _findRelated() {
    this._related = await findRelated(this.hass, "device", this.deviceId);
  }

  private _createScene() {
    const entities: SceneEntities = {};
    this._entities(this.deviceId, this._entityReg).forEach((entity) => {
      entities[entity.entity_id] = "";
    });
    showSceneEditor({
      entities,
    });
  }

  private _showScriptDialog() {
    showDeviceAutomationDialog(this, {
      device: this.hass.devices[this.deviceId],
      entityReg: this._entityReg,
      script: true,
    });
  }

  private _showAutomationDialog() {
    showDeviceAutomationDialog(this, {
      device: this.hass.devices[this.deviceId],
      entityReg: this._entityReg,
      script: false,
    });
  }

  private _renderIntegrationInfo(
    device: DeviceRegistryEntry,
    integrations: ConfigEntry[],
    deviceInfo: TemplateResult[]
  ) {
    const domains = integrations.map((int) => int.domain);
    if (domains.includes("zha")) {
      import("./device-detail/integration-elements/zha/ha-device-info-zha");
      deviceInfo.push(html`
        <ha-device-info-zha
          .hass=${this.hass}
          .device=${device}
        ></ha-device-info-zha>
      `);
    }
    if (domains.includes("zwave_js")) {
      import(
        "./device-detail/integration-elements/zwave_js/ha-device-info-zwave_js"
      );
      deviceInfo.push(html`
        <ha-device-info-zwave_js
          .hass=${this.hass}
          .device=${device}
        ></ha-device-info-zwave_js>
      `);
    }
    if (domains.includes("matter")) {
      import(
        "./device-detail/integration-elements/matter/ha-device-info-matter"
      );
      deviceInfo.push(html`
        <ha-device-info-matter
          .hass=${this.hass}
          .device=${device}
        ></ha-device-info-matter>
      `);
    }
  }

  private async _showSettings() {
    const device = this.hass.devices[this.deviceId];
    showDeviceRegistryDetailDialog(this, {
      device,
      updateEntry: async (updates) => {
        const oldDeviceName = device.name_by_user || device.name;
        const newDeviceName = updates.name_by_user;
        const disabled =
          updates.disabled_by === "user" && device.disabled_by !== "user";

        if (disabled) {
          for (const cnfg_entry of device.config_entries) {
            if (
              !Object.values(this.hass.devices).some(
                (dvc) =>
                  dvc.id !== device.id &&
                  dvc.config_entries.includes(cnfg_entry)
              )
            ) {
              const config_entry = this.entries.find(
                (entry) => entry.entry_id === cnfg_entry
              );
              if (
                config_entry &&
                !config_entry.disabled_by &&
                // eslint-disable-next-line no-await-in-loop
                (await showConfirmationDialog(this, {
                  title: this.hass.localize(
                    "ui.panel.config.devices.confirm_disable_config_entry",
                    { entry_name: config_entry.title }
                  ),
                  confirmText: this.hass.localize("ui.common.yes"),
                  dismissText: this.hass.localize("ui.common.no"),
                }))
              ) {
                let result: DisableConfigEntryResult;
                try {
                  // eslint-disable-next-line no-await-in-loop
                  result = await disableConfigEntry(this.hass, cnfg_entry);
                } catch (err: any) {
                  showAlertDialog(this, {
                    title: this.hass.localize(
                      "ui.panel.config.integrations.config_entry.disable_error"
                    ),
                    text: err.message,
                  });
                  return;
                }
                if (result.require_restart) {
                  showAlertDialog(this, {
                    text: this.hass.localize(
                      "ui.panel.config.integrations.config_entry.disable_restart_confirm"
                    ),
                  });
                }
                delete updates.disabled_by;
              }
            }
          }
        } else if (
          updates.disabled_by !== null &&
          updates.disabled_by !== "user"
        ) {
          delete updates.disabled_by;
        }
        try {
          await updateDeviceRegistryEntry(this.hass, this.deviceId, updates);
        } catch (err: any) {
          showAlertDialog(this, {
            title: this.hass.localize(
              "ui.panel.config.devices.update_device_error"
            ),
            text: err.message,
          });
          return;
        }

        if (
          !oldDeviceName ||
          !newDeviceName ||
          oldDeviceName === newDeviceName
        ) {
          return;
        }
        const entities = this._entities(this.deviceId, this._entityReg);

        const renameEntityid =
          this.showAdvanced &&
          (await showConfirmationDialog(this, {
            title: this.hass.localize(
              "ui.panel.config.devices.confirm_rename_entity_ids"
            ),
            text: this.hass.localize(
              "ui.panel.config.devices.confirm_rename_entity_ids_warning"
            ),
            confirmText: this.hass.localize("ui.common.rename"),
            dismissText: this.hass.localize("ui.common.no"),
            warning: true,
          }));

        const updateProms = entities.map((entity) => {
          const name = entity.name || entity.stateName;
          let newEntityId: string | undefined;
          let newName: string | null | undefined;

          let shouldUpdateName: boolean;
          let shouldUpdateEntityId = false;

          if (entity.has_entity_name && !entity.name) {
            shouldUpdateName = false;
          } else if (
            entity.has_entity_name &&
            (entity.name === oldDeviceName || entity.name === newDeviceName)
          ) {
            shouldUpdateName = true;
            // clear name if it matches the device name and it uses the device name (entity naming)
            newName = null;
          } else if (name && name.includes(oldDeviceName)) {
            shouldUpdateName = true;
            newName = name.replace(oldDeviceName, newDeviceName);
          } else {
            shouldUpdateName = false;
          }

          if (renameEntityid) {
            const oldSearch = slugify(oldDeviceName);
            if (entity.entity_id.includes(oldSearch)) {
              shouldUpdateEntityId = true;
              newEntityId = entity.entity_id.replace(
                oldSearch,
                slugify(newDeviceName)
              );
            }
          }

          if (newName === undefined && newEntityId === undefined) {
            return undefined;
          }

          return updateEntityRegistryEntry(this.hass!, entity.entity_id, {
            name: shouldUpdateName ? newName : undefined,
            new_entity_id: shouldUpdateEntityId ? newEntityId : undefined,
          });
        });
        await Promise.all(updateProms);
      },
    });
  }

  private async _enableDevice(): Promise<void> {
    await updateDeviceRegistryEntry(this.hass, this.deviceId, {
      disabled_by: null,
    });
  }

  private async _signUrl(ev) {
    const anchor = ev.currentTarget.closest("a");
    const signedUrl = await getSignedPath(
      this.hass,
      anchor.getAttribute("href")
    );
    fileDownload(signedUrl.path);
  }

  private _deviceActionClicked(ev) {
    if (!ev.currentTarget.action) {
      return;
    }

    ev.preventDefault();

    (ev.currentTarget as any).action(ev);
  }

  static get styles(): CSSResultGroup {
    return [
      haStyle,
      css`
        .container {
          display: flex;
          flex-wrap: wrap;
          margin: auto;
          max-width: 1000px;
          margin-top: 32px;
          margin-bottom: 32px;
        }

        .card-header {
          display: flex;
          align-items: center;
          justify-content: space-between;
          padding-bottom: 12px;
        }

        .card-header ha-icon-button {
          margin-right: -8px;
          margin-inline-end: -8px;
          margin-inline-start: initial;
          color: var(--primary-color);
          height: auto;
          direction: var(--direction);
        }

        .device-info {
          padding: 16px;
        }

        h1 {
          margin: 0;
          font-family: var(--paper-font-headline_-_font-family);
          -webkit-font-smoothing: var(
            --paper-font-headline_-_-webkit-font-smoothing
          );
          font-size: var(--paper-font-headline_-_font-size);
          font-weight: var(--paper-font-headline_-_font-weight);
          letter-spacing: var(--paper-font-headline_-_letter-spacing);
          line-height: var(--paper-font-headline_-_line-height);
          opacity: var(--dark-primary-opacity);
        }

        .header {
          display: flex;
          justify-content: space-between;
        }

        .header-name {
          display: flex;
          align-items: center;
          padding-left: 8px;
          padding-inline-start: 8px;
          direction: var(--direction);
        }

        .column,
        .fullwidth {
          padding: 8px;
          box-sizing: border-box;
        }
        .column {
          width: 33%;
          flex-grow: 1;
        }
        .fullwidth {
          width: 100%;
          flex-grow: 1;
        }

        .header-right {
          align-self: center;
        }

        .header-right img {
          height: 30px;
        }

        .header-right {
          display: flex;
        }

        .header-right:first-child {
          width: 100%;
          justify-content: flex-end;
        }

        .header-right > *:not(:first-child) {
          margin-left: 16px;
          margin-inline-start: 16px;
          margin-inline-end: initial;
          direction: var(--direction);
        }

        .battery {
          align-self: center;
          align-items: center;
          display: flex;
          white-space: nowrap;
        }

        .column > *:not(:first-child) {
          margin-top: 16px;
        }

        :host([narrow]) .column {
          width: 100%;
        }

        :host([narrow]) .container {
          margin-top: 0;
        }

        a {
          text-decoration: none;
          color: var(--primary-color);
        }

        ha-card a {
          color: var(--primary-text-color);
        }

        ha-svg-icon[slot="trailingIcon"] {
          display: block;
          width: 18px;
          height: 18px;
        }

        ha-svg-icon[slot="meta"] {
          width: 18px;
          height: 18px;
        }

        .items {
          padding-bottom: 16px;
        }

        ha-logbook {
          height: 400px;
        }
        :host([narrow]) ha-logbook {
          height: 235px;
        }

        .card-actions {
          display: flex;
          justify-content: space-between;
          align-items: center;
        }
      `,
    ];
  }
}

declare global {
  interface HTMLElementTagNameMap {
    "ha-config-device-page": HaConfigDevicePage;
  }
}
