import { mdiDotsVertical } from "@mdi/js";
import type { PropertyValues, TemplateResult } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import type { HASSDomEvent } from "../../../common/dom/fire_event";
import { navigate } from "../../../common/navigate";
import { extractSearchParam } from "../../../common/url/search-params";
import "../../../components/ha-dropdown";
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import "../../../components/ha-icon-button";
import "../../../components/input/ha-input-search";
import type {
  HassioAddonRepository,
  HassioAddonsInfo,
} from "../../../data/hassio/addon";
import {
  fetchHassioAddonsInfo,
  reloadHassioAddons,
} from "../../../data/hassio/addon";
import { extractApiErrorMessage } from "../../../data/hassio/common";
import type {
  StoreAddon,
  SupervisorStore,
} from "../../../data/supervisor/store";
import {
  addStoreRepository,
  fetchSupervisorStore,
} from "../../../data/supervisor/store";
import {
  showAlertDialog,
  showConfirmationDialog,
} from "../../../dialogs/generic/show-dialog-box";
import "../../../layouts/hass-error-screen";
import "../../../layouts/hass-loading-screen";
import "../../../layouts/hass-subpage";
import type { HomeAssistant, Route } from "../../../types";

import "./supervisor-apps-repository";

const sortRepos = (a: HassioAddonRepository, b: HassioAddonRepository) => {
  if (a.slug === "local") {
    return -1;
  }
  if (b.slug === "local") {
    return 1;
  }
  if (a.slug === "core") {
    return -1;
  }
  if (b.slug === "core") {
    return 1;
  }
  return a.name.toUpperCase() < b.name.toUpperCase() ? -1 : 1;
};

@customElement("ha-config-apps-available")
export class HaConfigAppsAvailable extends LitElement {
  @property({ attribute: false }) public hass!: HomeAssistant;

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

  @property({ attribute: false }) public route!: Route;

  @state() private _store?: SupervisorStore;

  @state() private _addon?: HassioAddonsInfo;

  @state() private _error?: string;

  @state() private _filter?: string;

  public connectedCallback(): void {
    super.connectedCallback();
    this.addEventListener(
      "apps-collection-refresh",
      this._handleCollectionRefresh as unknown as EventListener
    );
  }

  public disconnectedCallback(): void {
    super.disconnectedCallback();
    this.removeEventListener(
      "apps-collection-refresh",
      this._handleCollectionRefresh as unknown as EventListener
    );
  }

  protected firstUpdated(changedProps: PropertyValues<this>) {
    super.firstUpdated(changedProps);
    const repositoryUrl = extractSearchParam("repository_url");
    if (repositoryUrl) {
      navigate("/config/apps/available", { replace: true });
    }
    this._loadData().then(() => {
      if (repositoryUrl) {
        this._addRepository(repositoryUrl);
      }
    });
    this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev));
  }

  protected render() {
    if (this._error) {
      return html`
        <hass-error-screen
          .hass=${this.hass}
          .error=${this._error}
        ></hass-error-screen>
      `;
    }

    if (!this._store || !this._addon) {
      return html`
        <hass-loading-screen
          .hass=${this.hass}
          .narrow=${this.narrow}
        ></hass-loading-screen>
      `;
    }

    let repos: (TemplateResult | typeof nothing)[] = [];

    if (this._store.repositories) {
      repos = this._addonRepositories(
        this._store.repositories,
        this._store.addons,
        this._filter
      );
    }

    return html`
      <hass-subpage
        .hass=${this.hass}
        .narrow=${this.narrow}
        .route=${this.route}
        back-path="/config/apps"
        .header=${this.hass.localize("ui.panel.config.apps.store.title")}
      >
        <ha-dropdown slot="toolbar-icon" @wa-select=${this._handleAction}>
          <ha-icon-button
            .label=${this.hass.localize("ui.common.menu")}
            .path=${mdiDotsVertical}
            slot="trigger"
          ></ha-icon-button>
          <ha-dropdown-item value="check_updates">
            ${this.hass.localize("ui.panel.config.apps.store.check_updates")}
          </ha-dropdown-item>
          <ha-dropdown-item value="repositories">
            ${this.hass.localize("ui.panel.config.apps.store.repositories")}
          </ha-dropdown-item>
          <ha-dropdown-item value="registries">
            ${this.hass.localize("ui.panel.config.apps.store.registries")}
          </ha-dropdown-item>
        </ha-dropdown>
        ${
          repos.length === 0
            ? html`<hass-loading-screen no-toolbar></hass-loading-screen>`
            : html`
                <div class="search">
                  <ha-input-search
                    appearance="outlined"
                    .value=${this._filter}
                    @input=${this._filterChanged}
                  ></ha-input-search>
                </div>

                ${repos}
              `
        }
      </hass-subpage>
    `;
  }

  private _addonRepositories = memoizeOne(
    (
      repositories: HassioAddonRepository[],
      addons: StoreAddon[],
      filter?: string
    ) =>
      repositories.sort(sortRepos).map((repo) => {
        const filteredAddons = addons.filter(
          (addon) => addon.repository === repo.slug
        );

        return filteredAddons.length !== 0
          ? html`
              <supervisor-apps-repository
                .hass=${this.hass}
                .repo=${repo}
                .addons=${filteredAddons}
                .filter=${filter!}
              ></supervisor-apps-repository>
            `
          : nothing;
      })
  );

  private _handleAction(ev: HaDropdownSelectEvent) {
    const action = ev.detail.item.value;

    if (!action) {
      return;
    }

    switch (action) {
      case "check_updates":
        this._refreshData();
        break;
      case "repositories":
        this._manageRepositoriesClicked();
        break;
      case "registries":
        this._manageRegistries();
        break;
    }
  }

  private async _refreshData() {
    try {
      await reloadHassioAddons(this.hass);
    } catch (err) {
      showAlertDialog(this, {
        text: extractApiErrorMessage(err),
      });
    } finally {
      this._loadData();
    }
  }

  private _apiCalled(ev) {
    if (ev.detail.success) {
      this._loadData();
    }
  }

  private _manageRepositoriesClicked() {
    navigate("/config/apps/repositories");
  }

  private _manageRegistries() {
    navigate("/config/apps/registries");
  }

  private async _addRepository(repositoryUrl: string): Promise<void> {
    if (
      !this._store ||
      this._store.repositories.some((repo) => repo.source === repositoryUrl)
    ) {
      return;
    }

    if (
      !(await showConfirmationDialog(this, {
        title: this.hass.localize(
          "ui.panel.config.apps.my.add_repository_title"
        ),
        text: this.hass.localize(
          "ui.panel.config.apps.my.add_repository_store_description",
          { repository: repositoryUrl }
        ),
        confirmText: this.hass.localize("ui.common.add"),
        dismissText: this.hass.localize("ui.common.cancel"),
      }))
    ) {
      return;
    }

    try {
      await addStoreRepository(this.hass, repositoryUrl);
      await this._loadData();
    } catch (err: any) {
      showAlertDialog(this, {
        text: extractApiErrorMessage(err),
      });
    }
  }

  private async _loadData(): Promise<void> {
    try {
      const [addon, store] = await Promise.all([
        fetchHassioAddonsInfo(this.hass),
        fetchSupervisorStore(this.hass),
      ]);

      this._addon = addon;
      this._store = store;
    } catch (err: any) {
      this._error =
        err.message || this.hass.localize("ui.panel.config.apps.error_loading");
      showAlertDialog(this, {
        title: this.hass.localize("ui.panel.config.apps.error_loading"),
        text: this._error,
      });
    }
  }

  private _handleCollectionRefresh = async (
    ev: HASSDomEvent<{ collection: "addon" | "store" }>
  ): Promise<void> => {
    const { collection } = ev.detail;
    try {
      if (collection === "addon") {
        this._addon = await fetchHassioAddonsInfo(this.hass);
      } else if (collection === "store") {
        this._store = await fetchSupervisorStore(this.hass);
      }
    } catch (_err: any) {
      // Silently fail on refresh errors
    }
  };

  private _filterChanged(e: InputEvent) {
    this._filter = (e.target as HTMLInputElement).value;
  }

  static styles = css`
    :host {
      display: block;
      height: 100%;
      background-color: var(--primary-background-color);
    }
    supervisor-apps-repository {
      margin-top: 24px;
    }
    .search {
      position: sticky;
      top: 0;
      z-index: 2;
    }
    ha-input-search {
      padding: var(--ha-space-3) var(--ha-space-2);
      background: var(--sidebar-background-color);
      border-bottom: 1px solid var(--divider-color);
    }
  `;
}

declare global {
  interface HASSDomEvents {
    "apps-collection-refresh": { collection: "addon" | "store" };
  }
  interface HTMLElementTagNameMap {
    "ha-config-apps-available": HaConfigAppsAvailable;
  }
}
