import type { PropertyValues, TemplateResult } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { ifDefined } from "lit/directives/if-defined";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import type { HASSDomCurrentTargetEvent } from "../../../common/dom/fire_event";
import { computeDomain } from "../../../common/entity/compute_domain";
import "../../../components/entity/state-badge";
import "../../../components/ha-card";
import "../../../components/ha-icon";
import "../../../components/ha-relative-time";
import { UNAVAILABLE, UNKNOWN } from "../../../data/entity/entity";
import type { ActionHandlerEvent } from "../../../data/lovelace/action_handler";
import type {
  CallServiceActionConfig,
  MoreInfoActionConfig,
} from "../../../data/lovelace/config/action";
import {
  SENSOR_DEVICE_CLASS_UPTIME,
  SENSOR_TIMESTAMP_DEVICE_CLASSES,
} from "../../../data/sensor";
import type { HomeAssistant } from "../../../types";
import { actionHandler } from "../common/directives/action-handler-directive";
import { findEntities } from "../common/find-entities";
import { handleAction } from "../common/handle-action";
import { hasAction, hasAnyAction } from "../common/has-action";
import { hasConfigOrEntitiesChanged } from "../common/has-changed";
import { processConfigEntities } from "../common/process-config-entities";
import "../components/hui-timestamp-display";
import { createEntityNotFoundWarning } from "../components/hui-warning";
import "../components/hui-warning-element";
import type { LovelaceCard, LovelaceCardEditor } from "../types";
import type { GlanceCardConfig, GlanceConfigEntity } from "./types";
import { TIMESTAMP_STATE_DOMAINS } from "../../../common/const";

export const migrateGlanceCardConfig = (
  config: GlanceCardConfig
): GlanceCardConfig => {
  let changed = false;
  const newEntities = config.entities?.map((e) => {
    if (typeof e !== "object") {
      return e;
    }
    if (!("format" in e)) {
      return e;
    }
    changed = true;
    const { format, ...rest } = e;
    return {
      ...rest,
      time_format: rest.time_format ?? format,
    };
  });
  if (!changed) {
    return config;
  }
  return {
    ...config,
    entities: newEntities as (GlanceConfigEntity | string)[],
  };
};

@customElement("hui-glance-card")
export class HuiGlanceCard extends LitElement implements LovelaceCard {
  public static async getConfigElement(): Promise<LovelaceCardEditor> {
    await import("../editor/config-elements/hui-glance-card-editor");
    return document.createElement("hui-glance-card-editor");
  }

  public static getStubConfig(
    hass: HomeAssistant,
    entities: string[],
    entitiesFallback: string[]
  ): GlanceCardConfig {
    const includeDomains = ["sensor"];
    const maxEntities = 3;
    const foundEntities = findEntities(
      hass,
      maxEntities,
      entities,
      entitiesFallback,
      includeDomains
    );

    return { type: "glance", entities: foundEntities };
  }

  @property({ attribute: false }) public hass?: HomeAssistant;

  @state() private _config?: GlanceCardConfig;

  private _configEntities?: GlanceConfigEntity[];

  public getCardSize(): number {
    const rowHeight =
      (this._config!.show_icon ? 1 : 0) +
      (this._config!.show_name ? 1 : 0) +
      (this._config!.show_state ? 1 : 0);

    const numRows = Math.ceil(
      this._configEntities!.length / (this._config!.columns || 5)
    );

    return (this._config!.title ? 2 : 0) + rowHeight * numRows;
  }

  public setConfig(config: GlanceCardConfig): void {
    const migratedConfig = migrateGlanceCardConfig(config);
    this._config = {
      show_name: true,
      show_state: true,
      show_icon: true,
      state_color: true,
      ...migratedConfig,
    };
    const entities = processConfigEntities(migratedConfig.entities).map(
      (entityConf) => ({
        hold_action: { action: "more-info" } as MoreInfoActionConfig,
        ...entityConf,
      })
    );

    for (const entity of entities) {
      if (
        (entity.tap_action &&
          entity.tap_action.action === "call-service" &&
          !entity.tap_action.service) ||
        (entity.hold_action &&
          entity.hold_action.action === "call-service" &&
          !(entity.hold_action as CallServiceActionConfig).service)
      ) {
        throw new Error(
          'Missing required property "service" when tap_action or hold_action is call-service'
        );
      }
    }

    const columns =
      migratedConfig.columns || Math.min(migratedConfig.entities.length, 5);
    this.style.setProperty("--glance-column-width", `${100 / columns}%`);

    this._configEntities = entities;

    if (this.hass) {
      this.requestUpdate();
    }
  }

  protected shouldUpdate(changedProps: PropertyValues<this>): boolean {
    return hasConfigOrEntitiesChanged(this, changedProps);
  }

  protected render() {
    if (!this._config || !this.hass) {
      return nothing;
    }
    const { title } = this._config;

    return html`
      <ha-card .header=${title}>
        <div class=${classMap({ entities: true, "no-header": !title })}>
          ${this._configEntities!.map((entityConf) =>
            this._renderEntity(entityConf)
          )}
        </div>
      </ha-card>
    `;
  }

  protected updated(changedProps: PropertyValues): void {
    super.updated(changedProps);
    if (!this._config || !this.hass) {
      return;
    }

    const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
    const oldConfig = changedProps.get("_config") as
      | GlanceCardConfig
      | undefined;

    if (
      !oldHass ||
      !oldConfig ||
      oldHass.themes !== this.hass.themes ||
      oldConfig.theme !== this._config.theme
    ) {
      applyThemesOnElement(this, this.hass.themes, this._config.theme);
    }
  }

  static styles = css`
    ha-card {
      height: 100%;
    }
    .entities {
      display: flex;
      padding: 0 16px 4px;
      flex-wrap: wrap;
      box-sizing: border-box;
      align-items: center;
      align-content: center;
    }
    .entities.no-header {
      padding-top: 16px;
    }
    .entity {
      box-sizing: border-box;
      padding: 0 4px;
      display: flex;
      flex-direction: column;
      align-items: center;
      margin-bottom: 12px;
      width: var(--glance-column-width, 20%);
    }
    .entity.action {
      cursor: pointer;
    }
    .entity:focus {
      outline: none;
      background: var(--divider-color);
      border-radius: var(--ha-border-radius-lg);
      padding: 4px;
      margin-top: -4px;
      margin-bottom: 8px;
    }
    .entity div {
      width: 100%;
      text-align: center;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }
    .name {
      min-height: var(--ha-line-height-normal);
    }
    .warning {
      cursor: default;
      position: relative;
      padding: 8px;
      width: calc(var(--glance-column-width, 20%) - 4px);
      margin: 0 2px;
    }
    .warning::before {
      position: absolute;
      top: 0;
      right: 0;
      bottom: 0;
      left: 0;
      opacity: 0.12;
      pointer-events: none;
      content: "";
      border-radius: var(--ha-border-radius-sm);
      background-color: var(--warning-color);
    }
    state-badge {
      margin: 8px 0;
    }
    hui-warning-element {
      padding: 8px;
    }
  `;

  private _renderEntity(entityConf: GlanceConfigEntity): TemplateResult {
    const stateObj = this.hass!.states[entityConf.entity];

    if (!stateObj) {
      return html`<div class="entity warning">
        ${this._config!.show_name
          ? html`
              <div class="name">
                ${createEntityNotFoundWarning(this.hass!, entityConf.entity)}
              </div>
            `
          : ""}
        ${this._config!.show_icon
          ? html` <hui-warning-element
              .label=${createEntityNotFoundWarning(
                this.hass!,
                entityConf.entity
              )}
            ></hui-warning-element>`
          : ""}
        <div>${this._config!.show_state ? entityConf.entity : ""}</div>
      </div>`;
    }

    const name = this.hass!.formatEntityName(stateObj, entityConf.name);
    const domain = computeDomain(entityConf.entity);

    return html`
      <div
        class=${classMap({ entity: true, action: hasAnyAction(entityConf) })}
        .config=${entityConf}
        @action=${this._handleAction}
        .actionHandler=${actionHandler({
          hasHold: hasAction(entityConf.hold_action),
          hasDoubleClick: hasAction(entityConf.double_tap_action),
        })}
        tabindex=${ifDefined(
          !entityConf.tap_action || hasAction(entityConf.tap_action)
            ? "0"
            : undefined
        )}
      >
        ${this._config!.show_name
          ? html` <div class="name" .title=${name}>${name}</div> `
          : ""}
        ${this._config!.show_icon
          ? html`
              <state-badge
                .stateObj=${stateObj}
                .overrideIcon=${entityConf.icon}
                .overrideImage=${entityConf.image}
                .stateColor=${entityConf.state_color ??
                this._config!.state_color}
              ></state-badge>
            `
          : ""}
        ${this._config!.show_state && entityConf.show_state !== false
          ? html`
              <div>
                ${(TIMESTAMP_STATE_DOMAINS.has(domain) ||
                  (domain === "sensor" &&
                    SENSOR_TIMESTAMP_DEVICE_CLASSES.includes(
                      stateObj.attributes.device_class
                    ))) &&
                stateObj.state !== UNAVAILABLE &&
                stateObj.state !== UNKNOWN
                  ? html`
                      <hui-timestamp-display
                        .hass=${this.hass}
                        .ts=${new Date(stateObj.state)}
                        .format=${entityConf.time_format ??
                        (stateObj.attributes.device_class ===
                        SENSOR_DEVICE_CLASS_UPTIME
                          ? "total"
                          : undefined)}
                        capitalize
                      ></hui-timestamp-display>
                    `
                  : entityConf.show_last_changed
                    ? html`
                        <ha-relative-time
                          .datetime=${stateObj.last_changed}
                          capitalize
                        ></ha-relative-time>
                      `
                    : this.hass!.formatEntityState(stateObj)}
              </div>
            `
          : ""}
      </div>
    `;
  }

  private _handleAction(
    ev: HASSDomCurrentTargetEvent<
      HTMLElement & { config: GlanceConfigEntity }
    > &
      ActionHandlerEvent
  ) {
    const { config } = ev.currentTarget;
    handleAction(this, this.hass!, config, ev.detail.action!);
  }
}

declare global {
  interface HTMLElementTagNameMap {
    "hui-glance-card": HuiGlanceCard;
  }
}
