import deepClone from "deep-clone-simple";
import type { PropertyValues } from "lit";
import { ReactiveElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { storage } from "../../../common/decorators/storage";
import { deepEqual } from "../../../common/util/deep-equal";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { fireEvent } from "../../../common/dom/fire_event";
import { debounce } from "../../../common/util/debounce";
import "../../../components/ha-svg-icon";
import type { LovelaceSectionElement } from "../../../data/lovelace";
import type { LovelaceCardConfig } from "../../../data/lovelace/config/card";
import type {
  LovelaceSectionConfig,
  LovelaceSectionRawConfig,
} from "../../../data/lovelace/config/section";
import { isStrategySection } from "../../../data/lovelace/config/section";
import type { HomeAssistant } from "../../../types";
import { ConditionalListenerMixin } from "../../../mixins/conditional-listener-mixin";
import "../cards/hui-card";
import type { HuiCard } from "../cards/hui-card";
import { checkConditionsMet } from "../common/validate-condition";
import { createSectionElement } from "../create-element/create-section-element";
import { showCreateCardDialog } from "../editor/card-editor/show-create-card-dialog";
import { showEditCardDialog } from "../editor/card-editor/show-edit-card-dialog";
import { addCard, replaceCard } from "../editor/config-util";
import { performDeleteCard } from "../editor/delete-card";
import { parseLovelaceCardPath } from "../editor/lovelace-path";
import {
  checkStrategyShouldRegenerate,
  generateLovelaceSectionStrategy,
} from "../strategies/get-strategy";
import type { Lovelace } from "../types";
import { DEFAULT_SECTION_LAYOUT } from "./const";

declare global {
  interface HASSDomEvents {
    "section-visibility-changed": { value: boolean };
  }
}

@customElement("hui-section")
export class HuiSection extends ConditionalListenerMixin<LovelaceSectionConfig>(
  ReactiveElement
) {
  @property({ attribute: false }) public hass!: HomeAssistant;

  @property({ attribute: false }) public config!: LovelaceSectionRawConfig;

  @property({ attribute: false }) public lovelace?: Lovelace;

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

  @property({ type: Boolean, attribute: "import-only" })
  public importOnly = false;

  @property({ type: Number }) public index!: number;

  @property({ attribute: false }) public viewIndex!: number;

  @state() private _cards: HuiCard[] = [];

  private _layoutElementType?: string;

  private _layoutElement?: LovelaceSectionElement;

  @storage({
    key: "dashboardCardClipboard",
    state: false,
    subscribe: false,
    storage: "sessionStorage",
  })
  protected _clipboard?: LovelaceCardConfig;

  private _createCardElement(cardConfig: LovelaceCardConfig) {
    const element = document.createElement("hui-card");
    element.hass = this.hass;
    element.preview = this.preview;
    element.config = cardConfig;
    element.addEventListener("card-updated", (ev: Event) => {
      ev.stopPropagation();
      this._cards = [...this._cards];
    });
    element.load();
    return element;
  }

  protected createRenderRoot() {
    return this;
  }

  public willUpdate(changedProperties: PropertyValues<this>): void {
    super.willUpdate(changedProperties);

    /*
      We need to handle the following use cases:
       - initialization: create layout element, populate
       - config changed to section with same layout element
       - config changed to section with different layout element
       - forwarded properties hass/narrow/lovelace/cards change
          - cards change if one is rebuild when it was loaded later
          - lovelace changes if edit mode is enabled or config has changed
    */

    const oldConfig = changedProperties.get("config");

    // If config has changed, create element if necessary and set all values.
    if (
      changedProperties.has("config") &&
      (!oldConfig || this.config !== oldConfig)
    ) {
      this._initializeConfig();
      return;
    }

    if (!changedProperties.has("hass")) {
      return;
    }

    const oldHass = changedProperties.get("hass") as HomeAssistant | undefined;
    if (
      oldHass &&
      this.hass &&
      isStrategySection(this.config) &&
      this.hass.config.state === "RUNNING" &&
      (oldHass.config.state !== "RUNNING" ||
        checkStrategyShouldRegenerate(
          "section",
          this.config.strategy,
          oldHass,
          this.hass
        ))
    ) {
      this._debounceRefreshConfig();
    }
  }

  private _debounceRefreshConfig = debounce(
    () => this._initializeConfig(),
    200
  );

  public disconnectedCallback() {
    super.disconnectedCallback();
    this.removeEventListener(
      "card-visibility-changed",
      this._cardVisibilityChanged
    );
  }

  public connectedCallback() {
    super.connectedCallback();
    this._updateVisibility();
    this.addEventListener(
      "card-visibility-changed",
      this._cardVisibilityChanged
    );
    // Reapply theme on reconnect (e.g., after navigating away and back)
    if (this.hass && this._config?.theme) {
      applyThemesOnElement(this, this.hass.themes, this._config.theme);
    }
  }

  protected update(changedProperties: PropertyValues) {
    super.update(changedProperties);

    // If no layout element, we're still creating one
    if (this._layoutElement) {
      // Config has not changed. Just props
      if (changedProperties.has("hass")) {
        this._cards.forEach((element) => {
          element.hass = this.hass;
        });
        this._layoutElement.hass = this.hass;
        // React to theme or dark mode changes
        const oldHass = changedProperties.get("hass");
        if (
          !oldHass ||
          this.hass.themes !== oldHass.themes ||
          this.hass.selectedTheme !== oldHass.selectedTheme
        ) {
          applyThemesOnElement(this, this.hass.themes, this._config?.theme);
        }
      }
      if (changedProperties.has("lovelace")) {
        this._layoutElement.lovelace = this.lovelace;
      }
      if (changedProperties.has("preview")) {
        this._layoutElement.preview = this.preview;
        this._cards.forEach((element) => {
          element.preview = this.preview;
        });
      }
      if (changedProperties.has("importOnly")) {
        this._layoutElement.importOnly = this.importOnly;
      }
      if (changedProperties.has("_cards")) {
        this._layoutElement.cards = this._cards;
      }
      if (
        changedProperties.has("hass") ||
        changedProperties.has("preview") ||
        changedProperties.has("_cards")
      ) {
        this._updateVisibility();
      }
    }
  }

  private async _initializeConfig() {
    let sectionConfig = { ...this.config };
    let isStrategy = false;

    if (isStrategySection(sectionConfig)) {
      isStrategy = true;
      sectionConfig = await generateLovelaceSectionStrategy(
        sectionConfig,
        this.hass!
      );
    }

    sectionConfig = {
      ...sectionConfig,
      type: sectionConfig.type || DEFAULT_SECTION_LAYOUT,
    };

    if (isStrategy && deepEqual(sectionConfig, this._config)) {
      return;
    }

    this._config = sectionConfig;
    // Apply theme now that config is set (after potential strategy await)
    applyThemesOnElement(this, this.hass!.themes, this._config.theme);

    // Create a new layout element if necessary.
    let addLayoutElement = false;

    if (
      !this._layoutElement ||
      this._layoutElementType !== sectionConfig.type
    ) {
      addLayoutElement = true;
      this._createLayoutElement(this._config);
    } else {
      this._layoutElement.setConfig(sectionConfig);
    }

    this._createCards(sectionConfig);
    this._layoutElement!.isStrategy = isStrategy;
    this._layoutElement!.hass = this.hass;
    this._layoutElement!.lovelace = this.lovelace;
    this._layoutElement!.index = this.index;
    this._layoutElement!.viewIndex = this.viewIndex;
    this._layoutElement!.cards = this._cards;

    if (addLayoutElement) {
      while (this.lastChild) {
        this.removeChild(this.lastChild);
      }
      this._updateVisibility();
    }
  }

  private _cardVisibilityChanged = () => {
    this._updateVisibility();
  };

  protected _updateVisibility(conditionsMet?: boolean) {
    if (!this._layoutElement || !this._config) {
      return;
    }

    if (this.preview) {
      this._setElementVisibility(true);
      return;
    }

    if (this._config.disabled) {
      this._setElementVisibility(false);
      return;
    }

    const visible =
      conditionsMet ??
      (!this._config.visibility ||
        checkConditionsMet(
          this._config.visibility,
          this.hass,
          this._conditionContext
        ));

    if (!visible) {
      this._setElementVisibility(false);
      return;
    }

    // Hide section when all cards are conditionally hidden
    const allCardsHidden =
      this._cards.length > 0 && this._cards.every((card) => card.hidden);

    this._setElementVisibility(!allCardsHidden);
  }

  private _setElementVisibility(visible: boolean) {
    if (!this._layoutElement) return;

    if (this.hidden !== !visible) {
      this.style.setProperty("display", visible ? "" : "none");
      this.toggleAttribute("hidden", !visible);
      fireEvent(this, "section-visibility-changed", { value: visible });
    }

    // Always keep layout element connected so cards can still update
    // their visibility and bubble events back to the section.
    if (!this._layoutElement.parentElement) {
      this.appendChild(this._layoutElement);
    }
  }

  private _createLayoutElement(config: LovelaceSectionConfig): void {
    this._layoutElement = createSectionElement(
      config
    ) as LovelaceSectionElement;
    this._layoutElementType = config.type;
    this._layoutElement.addEventListener("ll-create-card", (ev) => {
      ev.stopPropagation();
      if (!this.lovelace) return;
      showCreateCardDialog(this, {
        lovelaceConfig: this.lovelace.config,
        saveConfig: this.lovelace.saveConfig,
        path: [this.viewIndex, this.index],
        suggestedCards: ev.detail?.suggested,
      });
    });
    this._layoutElement.addEventListener("ll-edit-card", (ev) => {
      ev.stopPropagation();
      if (!this.lovelace) return;
      const { cardIndex } = parseLovelaceCardPath(ev.detail.path);
      const sectionConfig = this.config;
      if (isStrategySection(sectionConfig)) {
        return;
      }
      const cardConfig = sectionConfig.cards![cardIndex];
      showEditCardDialog(this, {
        lovelaceConfig: this.lovelace.config,
        saveCardConfig: async (newCardConfig) => {
          const newConfig = replaceCard(
            this.lovelace!.config,
            [this.viewIndex, this.index, cardIndex],
            newCardConfig
          );
          await this.lovelace!.saveConfig(newConfig);
        },
        sectionConfig,
        cardConfig,
      });
    });
    this._layoutElement.addEventListener("ll-delete-card", (ev) => {
      ev.stopPropagation();
      if (!this.lovelace) return;
      performDeleteCard(this.hass, this.lovelace, ev.detail);
    });
    this._layoutElement.addEventListener("ll-duplicate-card", (ev) => {
      ev.stopPropagation();
      if (!this.lovelace) return;
      const { cardIndex } = parseLovelaceCardPath(ev.detail.path);
      const sectionConfig = this.config;
      if (isStrategySection(sectionConfig)) {
        return;
      }
      const cardConfig = sectionConfig.cards![cardIndex];

      showEditCardDialog(this, {
        lovelaceConfig: this.lovelace!.config,
        saveCardConfig: async (newCardConfig) => {
          const newConfig = addCard(
            this.lovelace!.config,
            [this.viewIndex, this.index],
            newCardConfig
          );
          await this.lovelace!.saveConfig(newConfig);
        },
        cardConfig,
        sectionConfig,
        isNew: true,
      });
    });
    this._layoutElement.addEventListener("ll-copy-card", (ev) => {
      ev.stopPropagation();
      if (!this.lovelace) return;
      const { cardIndex } = parseLovelaceCardPath(ev.detail.path);
      const sectionConfig = this.config;

      if (isStrategySection(sectionConfig)) {
        return;
      }
      const cardConfig = sectionConfig.cards![cardIndex];
      this._clipboard = deepClone(cardConfig);
    });
  }

  private _createCards(config: LovelaceSectionConfig): void {
    if (!config || !config.cards || !Array.isArray(config.cards)) {
      this._cards = [];
      return;
    }

    this._cards = config.cards.map((cardConfig) =>
      this._createCardElement(cardConfig)
    );
  }
}

declare global {
  interface HTMLElementTagNameMap {
    "hui-section": HuiSection;
  }
}
