import type { HassEntity } from 'home-assistant-js-websocket';
import type { TemplateResult } from 'lit';
import { html, LitElement } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { join } from 'lit/directives/join.js';

import { computeStateDomain, computeStateName } from '../../types/';
import type { HomeAssistant } from '../../types/ha-frontend';
import { isUnavailableState } from '../../types/ha-frontend/';
import { ensureArray } from '../../types/ha-frontend/common/array/ensure-array';
import { SENSOR_DEVICE_CLASS_TIMESTAMP } from '../../types/ha-frontend/data/sensor';
import type { UpdateEntity } from '../../types/ha-frontend/data/update';
import { computeUpdateStateDisplay } from '../../types/ha-frontend/data/update';

const TIMESTAMP_STATE_DOMAINS = ['button', 'input_button', 'scene'];

export const STATE_TEMPLATE_CONTENT = ['state_template'] as const;

export const STATE_DISPLAY_SPECIAL_CONTENT = ['remaining_time', 'install_status'] as const;

// Special handling of state attributes per domain
export const STATE_DISPLAY_SPECIAL_CONTENT_DOMAINS: Record<string, (typeof STATE_DISPLAY_SPECIAL_CONTENT)[number][]> = {
  timer: ['remaining_time'],
  update: ['install_status'],
};

// Attributes that should not be shown if their value is 0 */
export const HIDDEN_ZERO_ATTRIBUTES_DOMAINS: Record<string, string[]> = {
  valve: ['current_position'],
  cover: ['current_position'],
  fan: ['percentage'],
  light: ['brightness'],
};

type StateContent = string | string[];

export const DEFAULT_STATE_CONTENT_DOMAINS: Record<string, StateContent> = {
  climate: ['state', 'current_temperature'],
  cover: ['state', 'current_position'],
  fan: 'percentage',
  humidifier: ['state', 'current_humidity'],
  light: 'brightness',
  timer: 'remaining_time',
  update: 'install_status',
  valve: ['state', 'current_position'],
};

@customElement('vic-state-display')
class VicStateDisplay extends LitElement {
  @property({ attribute: false }) public hass!: HomeAssistant;

  @property({ attribute: false }) public stateObj!: HassEntity;

  @property({ attribute: false }) public content?: StateContent;

  @property({ attribute: false }) public name?: string;

  @property({ attribute: false }) public template?: string;

  @property({ type: Boolean, attribute: 'dash-unavailable' })
  public dashUnavailable?: boolean;

  protected createRenderRoot() {
    return this;
  }

  private get _content(): StateContent {
    const domain = computeStateDomain(this.stateObj);
    return this.content ?? DEFAULT_STATE_CONTENT_DOMAINS[domain] ?? 'state';
  }

  private _computeContent(content: string): TemplateResult<1> | string | undefined {
    const stateObj = this.stateObj;
    const domain = computeStateDomain(stateObj);

    if (content === 'state') {
      if (this.dashUnavailable && isUnavailableState(stateObj.state)) {
        return '—';
      }
      if (
        (stateObj.attributes.device_class === SENSOR_DEVICE_CLASS_TIMESTAMP ||
          TIMESTAMP_STATE_DOMAINS.includes(domain)) &&
        !isUnavailableState(stateObj.state)
      ) {
        return html`
          <hui-timestamp-display
            .hass=${this.hass}
            .ts=${new Date(stateObj.state)}
            format="relative"
            capitalize
          ></hui-timestamp-display>
        `;
      }

      return this.hass!.formatEntityState(stateObj);
    }

    if (content === 'state_template' && this.template !== undefined) {
      return html`${this.template.toString() || ''}`;
    }

    if (content === 'name') {
      return html`${this.name || computeStateName(stateObj)}`;
    }

    let relativeDateTime: string | undefined;

    // Check last-changed for backwards compatibility
    if (content === 'last_changed' || content === 'last-changed') {
      relativeDateTime = stateObj.last_changed;
    }
    // Check last_updated for backwards compatibility
    if (content === 'last_updated' || content === 'last-updated') {
      relativeDateTime = stateObj.last_updated;
    }

    if (
      content === 'last_triggered' ||
      (domain === 'calendar' && (content === 'start_time' || content === 'end_time')) ||
      (domain === 'sun' &&
        (content === 'next_dawn' ||
          content === 'next_dusk' ||
          content === 'next_midnight' ||
          content === 'next_noon' ||
          content === 'next_rising' ||
          content === 'next_setting'))
    ) {
      relativeDateTime = stateObj.attributes[content];
    }

    if (relativeDateTime) {
      return html` <ha-relative-time .hass=${this.hass} .datetime=${relativeDateTime} capitalize></ha-relative-time> `;
    }

    const specialContent = (STATE_DISPLAY_SPECIAL_CONTENT_DOMAINS[domain] ?? []) as string[];

    if (specialContent.includes(content)) {
      if (content === 'install_status') {
        return html` ${computeUpdateStateDisplay(stateObj as UpdateEntity, this.hass!)} `;
      }
      if (content === 'remaining_time') {
        return html` <ha-timer-remaining-time .hass=${this.hass} .stateObj=${stateObj}></ha-timer-remaining-time> `;
      }
    }

    const attribute = stateObj.attributes[content];

    if (attribute == null || (HIDDEN_ZERO_ATTRIBUTES_DOMAINS[domain]?.includes(content) && !attribute)) {
      return undefined;
    }
    return this.hass!.formatEntityAttributeValue(stateObj, content);
  }

  protected render() {
    const stateObj = this.stateObj;
    const contents = ensureArray(this._content);

    const values = contents.map((content) => this._computeContent(content)).filter(Boolean);

    if (!values.length) {
      return html`${this.hass!.formatEntityState(stateObj)}`;
    }

    return join(values, ' · ');
  }
}

declare global {
  interface HTMLElementTagNameMap {
    'vic-state-display': VicStateDisplay;
  }
}
