import { LitElement, html, nothing } from 'lit';
import { customElement, query, state } from 'lit/decorators.js';
import type { DhInput, DhSelect } from '@dh-software/dh-components';
import { EINSTUFUNGEN, type Einstufung, type Software } from 'bibliothek-types';
import { ladeKatalog, ladeUmgebung, softwareLoeschen, abgleichen, alleAbgleichen, downloadUrl, downloadMehrereUrl } from '../api-client';
import { gemerkterModus, setzeTheme, type ThemeModus } from '../theme';
import './bibliothek-zeile';
import './bibliothek-formular';
import './bibliothek-einstellungen';

function themeOptionen(aktuell: ThemeModus) {
  // Frische Kopie je dh-select (die Komponente mutiert die Options-Objekte).
  return [
    { dataId: 'system', showContent: 'System', selected: aktuell === 'system' },
    { dataId: 'hell', showContent: 'Hell', selected: aktuell === 'hell' },
    { dataId: 'dunkel', showContent: 'Dunkel', selected: aktuell === 'dunkel' },
  ];
}

/** Wurzel-Komponente: Katalog als kompakte, durchsuchbare Liste; Verwaltung + Abgleich nur lokal. */
@customElement('bibliothek-app')
export class BibliothekApp extends LitElement {
  @query('#theme-select') private themeSelect?: DhSelect;

  @state() private katalog: Software[] = [];
  @state() private lokal = false;
  @state() private ladefehler = '';
  @state() private formularOffen = false;
  @state() private einstellungenOffen = false;
  @state() private zuBearbeiten?: Software;
  @state() private suchbegriff = '';
  @state() private beschaeftigt = false;
  @state() private themeModus: ThemeModus = gemerkterModus();
  @state() private aktiveEinstufung: Einstufung = 'lizenzfrei';
  /** Ids der für den Sammel-Download angehakten Einträge — bleibt über Reiter hinweg bestehen. */
  @state() private auswahl: ReadonlySet<number> = new Set();

  // Light DOM, damit die globalen dh-components-Theme-Regeln greifen.
  createRenderRoot() {
    return this;
  }

  connectedCallback(): void {
    super.connectedCallback();
    this.datenLaden();
  }

  protected firstUpdated(): void {
    if (this.themeSelect) {
      (this.themeSelect as unknown as { selectOptions: ReturnType<typeof themeOptionen> }).selectOptions = themeOptionen(this.themeModus);
    }
  }

  render() {
    return html`
      <div class="bibliothek-app-rahmen">
        <header class="kopf">
          <h1>Software-Bibliothek</h1>
          <div class="kopf-aktionen">
            <dh-select
              id="theme-select"
              class="theme-select"
              withoutEmptyDefault
              notSearchable
              @changeSelectOption=${this.beiThemeWechsel}
            ></dh-select>
            ${this.lokal && !this.formularOffen && !this.einstellungenOffen
              ? html`
                  <dh-button buttonText="Alle prüfen" variant="standard" ?disabled=${this.beschaeftigt} @click=${this.allePruefen}></dh-button>
                  <dh-button buttonText="+ Neue Software" variant="filled" @click=${this.neuAnlegen}></dh-button>
                  <dh-button icon-button title="Einstellungen" variant="standard" @click=${this.einstellungenOeffnen}>
                    <span slot="img" class="fv-icon-settings"></span>
                  </dh-button>
                `
              : nothing}
          </div>
        </header>
        ${this.beschaeftigt ? html`<p class="hinweis">Bezugsquellen werden geprüft …</p>` : nothing}
        ${this.rendereProblemleiste()}
        ${this.auswahl.size > 0 && !this.formularOffen
          ? html`<div class="auswahl-leiste">
              <dh-button icon-button variant="standard" title="Auswahl aufheben" @click=${this.auswahlAufheben}>
                <span slot="img" class="fv-icon-close"></span>
              </dh-button>
              <span class="auswahl-anzahl">${this.auswahl.size} ausgewählt</span>
              <dh-button buttonText="Herunterladen" variant="filled" @click=${this.auswahlHerunterladen}></dh-button>
            </div>`
          : nothing}
        ${this.ladefehler ? html`<p class="fehler">${this.ladefehler}</p>` : nothing}
        ${this.einstellungenOffen
          ? html`<bibliothek-einstellungen
              @geschlossen=${this.einstellungenSchliessen}
              @umzugAbgeschlossen=${this.datenLaden}
            ></bibliothek-einstellungen>`
          : this.formularOffen
          ? html`<bibliothek-formular
              .software=${this.zuBearbeiten}
              .moeglicheEltern=${this.hauptEintraege()}
              @gespeichert=${this.beiGespeichert}
              @abgebrochen=${this.formularSchliessen}
            ></bibliothek-formular>`
          : this.rendereKatalog()}
      </div>
    `;
  }

  /**
   * Weist auf Einträge hin, deren letzter Abgleich fehlgeschlagen ist — sonst
   * fällt ein toter Bezugslink nur auf, wenn jemand gezielt in die Liste schaut.
   */
  private rendereProblemleiste() {
    const betroffene = this.katalog.filter((software) => software.letzterAbgleich && !software.abgleichErfolg);
    if (betroffene.length === 0) {
      return nothing;
    }
    const namen = betroffene.slice(0, 3).map((software) => software.name).join(', ');
    const weitere = betroffene.length > 3 ? ` und ${betroffene.length - 3} weitere` : '';
    // Der Hinweis auf den erhaltenen Stand ergibt nur Sinn, wenn es überhaupt einen gibt.
    const mitBisherigerDatei = betroffene.some((software) => software.dateiname);
    return html`<p class="warnung">
      <strong>${betroffene.length} Eintrag${betroffene.length === 1 ? '' : 'e'} mit Problemen beim Abgleich:</strong>
      ${namen}${weitere}.${mitBisherigerDatei ? ' Die zuletzt bereitgestellte Datei bleibt dabei erhalten.' : ''}
    </p>`;
  }

  private rendereKatalog() {
    if (this.katalog.length === 0) {
      return html`<p>Noch keine Software im Katalog.</p>`;
    }
    return html`
      <dh-input
        class="suche"
        type="text"
        placeholder="Suchen"
        .value=${this.suchbegriff}
        @input=${this.beiSuche}
      ></dh-input>
      <dh-tab stretch @clickTab=${this.beiReiterWechsel}>
        ${EINSTUFUNGEN.map(
          (eintrag) => html`<div
            slot="tab-head"
            content=${eintrag.id}
            class=${eintrag.id === this.aktiveEinstufung ? 'selected' : ''}
          >
            ${eintrag.text} (${this.eintraegeMitEinstufung(eintrag.id).length})
          </div>`
        )}
        ${EINSTUFUNGEN.map(
          (eintrag) => html`<div slot="tab-content" tab=${eintrag.id} class=${eintrag.id === this.aktiveEinstufung ? '' : 'hidden'}>
            ${eintrag.id === this.aktiveEinstufung ? this.rendereListe() : nothing}
          </div>`
        )}
      </dh-tab>
    `;
  }

  private rendereListe() {
    const gefiltert = this.gefilterterKatalog();
    if (gefiltert.length === 0) {
      return html`<p class="leer">
        ${this.suchbegriff ? html`Keine Treffer für „${this.suchbegriff}".` : 'In dieser Einstufung ist noch nichts hinterlegt.'}
      </p>`;
    }
    return html`
      ${this.aktiveEinstufung === 'lizenzpflichtig'
        ? html`<p class="hinweis">Für diese Programme kommt der Lizenzschlüssel über den Support.</p>`
        : nothing}
      <div class="liste ${this.lokal ? 'verwaltung' : ''}">
      ${gefiltert.map(
        (software) => html`<bibliothek-zeile
          .software=${software}
          .zusatzinhalte=${this.zusatzinhalteVon(software.id)}
          .auswahl=${this.auswahl}
          ?gewaehlt=${this.auswahl.has(software.id)}
          ?lokal=${this.lokal}
          @auswahlGeaendert=${this.beiAuswahlGeaendert}
          @herunterladen=${this.beiHerunterladen}
          @pruefen=${this.beiPruefen}
          @bearbeiten=${this.beiBearbeiten}
          @loeschen=${this.beiLoeschen}
        ></bibliothek-zeile>`
      )}
    </div>`;
  }

  private beiAuswahlGeaendert(ereignis: CustomEvent<{ software: Software; gewaehlt: boolean }>): void {
    const { software, gewaehlt } = ereignis.detail;
    const neueAuswahl = new Set(this.auswahl);
    if (gewaehlt) {
      neueAuswahl.add(software.id);
    } else {
      neueAuswahl.delete(software.id);
    }
    this.auswahl = neueAuswahl;
  }

  private auswahlAufheben(): void {
    this.auswahl = new Set();
  }

  private auswahlHerunterladen(): void {
    const angegebenerName = this.namenErfragen();
    if (angegebenerName === null) {
      return;
    }
    window.location.href = downloadMehrereUrl([...this.auswahl], angegebenerName);
  }

  /** Fragt im Netzwerk-Betrieb nach dem Namen fürs Protokoll; lokal nicht nötig. */
  private namenErfragen(): string | undefined | null {
    if (this.lokal) {
      return undefined;
    }
    return prompt('Bitte deinen Namen angeben (wird protokolliert):');
  }

  private beiReiterWechsel(ereignis: CustomEvent<string>): void {
    this.aktiveEinstufung = ereignis.detail as Einstufung;
  }

  /** Eigenständige Tools einer Einstufung (Grundlage für die Zähler an den Reitern). */
  private eintraegeMitEinstufung(einstufung: Einstufung): Software[] {
    return this.hauptEintraege().filter((software) => software.einstufung === einstufung);
  }

  /** Eigenständige Tools (ohne die Zusatzinhalte, die unter ihrem Tool erscheinen). */
  private hauptEintraege(): Software[] {
    return this.katalog.filter((software) => !software.elternId);
  }

  private zusatzinhalteVon(elternId: number): Software[] {
    return this.katalog.filter((software) => software.elternId === elternId);
  }

  /** Einträge des aktiven Reiters, zusätzlich nach dem Suchbegriff gefiltert. */
  private gefilterterKatalog(): Software[] {
    const eintraege = this.eintraegeMitEinstufung(this.aktiveEinstufung);
    const begriff = this.suchbegriff.trim().toLowerCase();
    if (!begriff) {
      return eintraege;
    }
    return eintraege.filter(
      (software) => software.name.toLowerCase().includes(begriff) || software.beschreibung.toLowerCase().includes(begriff)
    );
  }

  private beiSuche(ereignis: Event): void {
    this.suchbegriff = (ereignis.target as DhInput).value;
  }

  private beiThemeWechsel(ereignis: CustomEvent<{ selectedOption: { dataId: string } }>): void {
    this.themeModus = ereignis.detail.selectedOption.dataId as ThemeModus;
    setzeTheme(this.themeModus);
  }

  public async datenLaden(): Promise<void> {
    try {
      const [umgebung, katalog] = await Promise.all([ladeUmgebung(), ladeKatalog()]);
      this.lokal = umgebung.lokal;
      this.katalog = katalog;
      this.ladefehler = '';
    } catch (fehler) {
      this.ladefehler = fehler instanceof Error ? fehler.message : 'Daten konnten nicht geladen werden.';
    }
  }

  private neuAnlegen(): void {
    this.zuBearbeiten = undefined;
    this.formularOffen = true;
  }

  private einstellungenOeffnen(): void {
    this.einstellungenOffen = true;
  }

  private einstellungenSchliessen(): void {
    this.einstellungenOffen = false;
  }

  private beiBearbeiten(ereignis: CustomEvent<Software>): void {
    this.zuBearbeiten = ereignis.detail;
    this.formularOffen = true;
  }

  private formularSchliessen(): void {
    this.formularOffen = false;
    this.zuBearbeiten = undefined;
  }

  private beiGespeichert(): void {
    this.formularSchliessen();
    this.datenLaden();
  }

  private beiHerunterladen(ereignis: CustomEvent<Software>): void {
    const angegebenerName = this.namenErfragen();
    if (angegebenerName === null) {
      return;
    }
    window.location.href = downloadUrl(ereignis.detail.id, angegebenerName);
  }

  private async beiPruefen(ereignis: CustomEvent<Software>): Promise<void> {
    await this.mitBeschaeftigt(() => abgleichen(ereignis.detail.id));
    this.datenLaden();
  }

  private async allePruefen(): Promise<void> {
    await this.mitBeschaeftigt(() => alleAbgleichen());
    this.datenLaden();
  }

  private async beiLoeschen(ereignis: CustomEvent<Software>): Promise<void> {
    const software = ereignis.detail;
    const anzahlZusatzinhalte = this.zusatzinhalteVon(software.id).length;
    const zusatz = anzahlZusatzinhalte > 0 ? ` Die ${anzahlZusatzinhalte} zugehörigen Zusatzinhalte werden mitgelöscht.` : '';
    if (!confirm(`„${software.name}" wirklich löschen?${zusatz}`)) {
      return;
    }
    try {
      await softwareLoeschen(software.id);
      this.datenLaden();
    } catch (fehler) {
      this.ladefehler = fehler instanceof Error ? fehler.message : 'Löschen fehlgeschlagen.';
    }
  }

  private async mitBeschaeftigt(aktion: () => Promise<unknown>): Promise<void> {
    this.beschaeftigt = true;
    this.ladefehler = '';
    try {
      await aktion();
    } catch (fehler) {
      this.ladefehler = fehler instanceof Error ? fehler.message : 'Abgleich fehlgeschlagen.';
    } finally {
      this.beschaeftigt = false;
    }
  }
}

declare global {
  interface HTMLElementTagNameMap {
    'bibliothek-app': BibliothekApp;
  }
}
