import { LitElement, html, nothing } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import { event, type EventRef } from '@dh-software/lit-extensions';
import type { DhInput } from '@dh-software/dh-components';
import {
  EINSTUFUNGEN,
  PROGRAMMARTEN,
  type Einstufung,
  type GeheimnisUebersicht,
  type Programmart,
  type QuelleTyp,
  type Software,
  type SoftwareEingabe,
} from 'bibliothek-types';
import { softwareAnlegen, softwareAktualisieren, dateiHochladen, ladeGeheimnisse, speichereGeheimnisse } from '../api-client';

interface QuellenOption {
  dataId: string;
  showContent: string;
  selected?: boolean;
}

/**
 * dh-select verwirft Klicks auf Einträge ohne dataId (`if (!dataId) return;`) und lässt
 * die Liste dann offen stehen. „Keine Quelle" braucht deshalb eine eigene Kennung, die
 * beim Lesen und Schreiben auf den Leerwert übersetzt wird.
 */
const OHNE_QUELLE = 'keine';

const QUELLEN_OPTIONEN: QuellenOption[] = [
  { dataId: OHNE_QUELLE, showContent: 'Keine (nur Katalog-Eintrag)' },
  { dataId: 'manuell', showContent: 'Manuell — Datei hochladen (kein Auto-Update)' },
  { dataId: 'direkt', showContent: 'Direkt-Download-Link' },
  { dataId: 'github', showContent: 'GitHub-Release' },
  { dataId: 'winget', showContent: 'winget-Paket' },
  { dataId: 'svn', showContent: 'SVN-Export' },
  { dataId: 'git', showContent: 'Git-Repository — wird selbst gebaut' },
];

const WERT_PLATZHALTER: Record<string, string> = {
  '': 'zuerst eine Quelle wählen',
  direkt: 'https://…/setup.exe',
  github: 'besitzer/projekt  (z.B. ip7z/7zip)',
  winget: 'Paket-ID  (z.B. Google.Chrome)',
  svn: 'https://svn…/pfad/datei.exe',
  git: 'ssh://git.server.de:29418/gruppe/tool.git   (optional mit #zweig)',
};

/** Was die gewählte Programmart für den Nutzer bedeutet — steht als Hilfstext unter dem Feld. */
const PROGRAMMART_ERLAEUTERUNG: Record<Programmart, string> = {
  unbestimmt: 'Noch nicht eingeordnet — am Eintrag erscheint kein Hinweis',
  installer: 'Am Eintrag erscheint: Datei kann nach der Installation gelöscht werden',
  eigenstaendig: 'Am Eintrag erscheint: läuft direkt, an einen festen Ort legen',
};

/** Formular zum Anlegen/Bearbeiten einer Software inkl. Bezugsquelle. */
@customElement('bibliothek-formular')
export class BibliothekFormular extends LitElement {
  @property({ attribute: false }) software?: Software;
  /** Eigenständige Tools, denen dieser Eintrag als Plug-in zugeordnet werden kann. */
  @property({ attribute: false }) moeglicheEltern: Software[] = [];

  @state() private name = '';
  @state() private beschreibung = '';
  @state() private elternId = 0;
  @state() private elternOptionen: QuellenOption[] = [];
  @state() private quelleTyp: QuelleTyp | '' = '';
  @state() private quelleWert = '';
  @state() private bauBefehl = '';
  @state() private bauErgebnis = '';
  @state() private einstufung: Einstufung = 'lizenzfrei';
  @state() private einstufungsOptionen: QuellenOption[] = [];
  @state() private programmart: Programmart = 'unbestimmt';
  @state() private programmartOptionen: QuellenOption[] = [];
  @state() private hinweis = '';
  @state() private anleitungUrl = '';
  @state() private erwarteterHerausgeber = '';
  @state() private geheimnisStand: GeheimnisUebersicht[] = [];
  @state() private geheimwerte: Record<string, string> = {};
  @state() private datei?: File;
  @state() private quellenOptionen: QuellenOption[] = QUELLEN_OPTIONEN.map((option) => ({ ...option }));
  @state() private fehler = '';
  @state() private speichert = false;

  @event({ bubbles: true, composed: true }, 'gespeichert')
  private gespeichert!: EventRef<void>;

  @event({ bubbles: true, composed: true }, 'abgebrochen')
  private abgebrochen!: EventRef<void>;

  // Light DOM, damit das globale dh-components-Theme greift.
  createRenderRoot() {
    return this;
  }

  connectedCallback(): void {
    super.connectedCallback();
    if (this.software) {
      this.name = this.software.name;
      this.beschreibung = this.software.beschreibung;
      this.elternId = this.software.elternId;
      this.quelleTyp = this.software.quelleTyp;
      this.quelleWert = this.software.quelleWert;
      this.bauBefehl = this.software.bauBefehl;
      this.bauErgebnis = this.software.bauErgebnis;
      this.einstufung = this.software.einstufung;
      this.programmart = this.software.programmart;
      this.hinweis = this.software.hinweis;
      this.anleitungUrl = this.software.anleitungUrl;
      this.erwarteterHerausgeber = this.software.erwarteterHerausgeber;
      this.quellenOptionen = QUELLEN_OPTIONEN.map((option) => ({
        ...option,
        selected: option.dataId === (this.quelleTyp || OHNE_QUELLE),
      }));
    }
    this.einstufungsOptionen = EINSTUFUNGEN.map((eintrag) => ({
      dataId: eintrag.id,
      showContent: eintrag.text,
      selected: eintrag.id === this.einstufung,
    }));
    this.programmartOptionen = PROGRAMMARTEN.map((eintrag) => ({
      dataId: eintrag.id,
      showContent: eintrag.text,
      selected: eintrag.id === this.programmart,
    }));
    if (this.software) {
      void this.geheimnisStandLaden(this.software.id);
    }
    // Sich selbst nicht als eigenes Eltern-Tool anbieten.
    this.elternOptionen = [
      { dataId: '0', showContent: 'Eigenständiges Tool', selected: !this.elternId },
      ...this.moeglicheEltern
        .filter((tool) => tool.id !== this.software?.id)
        .map((tool) => ({ dataId: String(tool.id), showContent: tool.name, selected: tool.id === this.elternId })),
    ];
  }

  render() {
    return html`
      <dh-card variant="outlined" no-hover-effect no-click-effect>
        <span slot="header">${this.software ? 'Software bearbeiten' : 'Neue Software'}</span>
        <dh-form-grid slot="content">
          <dh-form-field label="Name">
            <dh-input type="text" .value=${this.name} @input=${this.beiName}></dh-input>
          </dh-form-field>
          <dh-form-field label="Einstufung">
            <dh-select
              withoutEmptyDefault
              notSearchable
              .selectOptions=${this.einstufungsOptionen}
              @changeSelectOption=${this.beiEinstufung}
            ></dh-select>
          </dh-form-field>
          <dh-form-field label="Programmart">
            <dh-select
              withoutEmptyDefault
              notSearchable
              .selectOptions=${this.programmartOptionen}
              @changeSelectOption=${this.beiProgrammart}
            ></dh-select>
            <span class="feldhinweis">${PROGRAMMART_ERLAEUTERUNG[this.programmart]}</span>
          </dh-form-field>
          <dh-form-field label="Gehört zu">
            <dh-select
              withoutEmptyDefault
              notSearchable
              .selectOptions=${this.elternOptionen}
              @changeSelectOption=${this.beiEltern}
            ></dh-select>
            <span class="feldhinweis">Zusatzinhalte (Plug-ins, Sprachpakete, Vorlagen …) erscheinen unter dem gewählten Tool</span>
          </dh-form-field>
          <dh-form-field label="Bezugsquelle">
            <dh-select
              withoutEmptyDefault
              notSearchable
              .selectOptions=${this.quellenOptionen}
              @changeSelectOption=${this.beiQuelleTyp}
            ></dh-select>
          </dh-form-field>
          ${this.rendereQuellenAngabe()} ${this.rendereBauFelder()} ${this.rendereGeheimnisse()}
          <dh-form-field label="Anleitung (Link, optional)">
            <dh-input type="text" placeholder="https://wiki…" .value=${this.anleitungUrl} @input=${this.beiAnleitung}></dh-input>
          </dh-form-field>
          <dh-form-field label="Hinweis (optional)">
            <dh-input
              type="text"
              placeholder="z. B. braucht Adminrechte"
              .value=${this.hinweis}
              @input=${this.beiHinweis}
            ></dh-input>
            <span class="feldhinweis">Erscheint am Eintrag im Katalog</span>
          </dh-form-field>
          <dh-form-field label="Erwarteter Signaturgeber (optional)">
            <dh-input
              type="text"
              placeholder="z. B. NOTEPAD++"
              .value=${this.erwarteterHerausgeber}
              @input=${this.beiErwarteterHerausgeber}
            ></dh-input>
            <span class="feldhinweis">Weicht die Signatur der geholten Datei davon ab, wird sie nicht zum Download angeboten.</span>
          </dh-form-field>
          <dh-form-field label="Beschreibung" span="2">
            <dh-textarea .value=${this.beschreibung} @isChange=${this.beiBeschreibung}></dh-textarea>
          </dh-form-field>
        </dh-form-grid>
        <div slot="actions">
          ${this.fehler ? html`<span class="fehler">${this.fehler}</span>` : ''}
          <dh-button buttonText="Abbrechen" variant="standard" @click=${this.beiAbbrechen}></dh-button>
          <dh-button
            buttonText=${this.speichert ? 'Speichert…' : 'Speichern'}
            variant="filled"
            ?disabled=${this.speichert}
            @click=${this.beiSpeichern}
          ></dh-button>
        </div>
      </dh-card>
    `;
  }

  /** Je nach Quelle: Datei-Auswahl (manuell) oder Textfeld für die Quell-Angabe. */
  private rendereQuellenAngabe() {
    if (this.quelleTyp === 'manuell') {
      return html`
        <dh-form-field label="Datei">
          <input type="file" @change=${this.beiDatei} />
          ${this.software?.dateiname ? html`<span class="feldhinweis">Aktuell hinterlegt: ${this.software.dateiname}</span>` : ''}
        </dh-form-field>
      `;
    }
    return html`
      <dh-form-field label="Quell-Angabe">
        <dh-input
          type="text"
          placeholder=${WERT_PLATZHALTER[this.quelleTyp]}
          ?disabled=${this.quelleTyp === ''}
          .value=${this.quelleWert}
          @input=${this.beiQuelleWert}
        ></dh-input>
        ${this.quelleTyp === 'git' && this.quelleWert.includes('@')
          ? html`<span class="feldhinweis warnhinweis">
              Die URL enthält einen Benutzernamen. Besser ohne — dann liefert die SSH-Konfiguration des
              Servers die Kennung, und ein Kontowechsel erfordert keine Änderung an den Einträgen.
            </span>`
          : nothing}
      </dh-form-field>
    `;
  }

  /**
   * Bau-Geheimnisse: Das Repository deklariert in `software-bibliothek.json`, was es
   * braucht; hier wird nur der Wert hinterlegt. Bereits gespeicherte Werte werden nie
   * zurückgeliefert — ein leer gelassenes Feld lässt den bisherigen Wert unverändert.
   */
  private rendereGeheimnisse() {
    if (this.quelleTyp !== 'git' || !this.software) {
      return nothing;
    }
    const bedarf = this.software.benoetigteGeheimnisse ?? [];
    if (bedarf.length === 0) {
      return html`<dh-form-field label="Schlüssel für den Bau" span="2">
        <span class="feldhinweis">
          Dieses Repository fordert keine an. Bedarf wird in <code>software-bibliothek.json</code> unter
          <code>secrets</code> deklariert und beim Bau als Umgebungsvariable bereitgestellt.
        </span>
      </dh-form-field>`;
    }
    return bedarf.map(
      (eintrag) => html`<dh-form-field label=${eintrag.name} span="2">
        <dh-input
          type="password"
          toggle-password
          placeholder=${this.istHinterlegt(eintrag.name) ? 'hinterlegt — leer lassen, um ihn zu behalten' : 'noch nicht hinterlegt'}
          .value=${this.geheimwerte[eintrag.name] ?? ''}
          @input=${(ereignis: Event) => this.beiGeheimwert(eintrag.name, ereignis)}
        ></dh-input>
        <span class="feldhinweis">${eintrag.beschreibung || 'Wird dem Bau als Umgebungsvariable übergeben.'}</span>
      </dh-form-field>`
    );
  }

  private istHinterlegt(name: string): boolean {
    return this.geheimnisStand.some((eintrag) => eintrag.name === name && eintrag.hinterlegt);
  }

  private beiGeheimwert(name: string, ereignis: Event): void {
    this.geheimwerte = { ...this.geheimwerte, [name]: (ereignis.target as DhInput).value };
  }

  /** Nur für die Git-Quelle: Bau-Befehl und Pfad des Ergebnisses. */
  private rendereBauFelder() {
    if (this.quelleTyp !== 'git') {
      return nothing;
    }
    return html`
      <dh-form-field label="Bau-Befehl (optional)" span="2">
        <dh-input
          type="text"
          placeholder="kommt normalerweise aus software-bibliothek.json im Repository"
          .value=${this.bauBefehl}
          @input=${this.beiBauBefehl}
        ></dh-input>
        <span class="feldhinweis">
          Das Repository beschreibt seinen Bau selbst in <code>software-bibliothek.json</code>
          (<code>bauBefehl</code> und <code>ergebnis</code>). Nur ausfüllen, um das zu übersteuern.
        </span>
      </dh-form-field>
      <dh-form-field label="Ergebnis-Datei (optional)" span="2">
        <dh-input type="text" placeholder="z. B. dist/MeinTool.exe" .value=${this.bauErgebnis} @input=${this.beiBauErgebnis}></dh-input>
        <span class="feldhinweis">Pfad relativ zur Arbeitskopie; im Dateinamen ist ein * erlaubt.</span>
      </dh-form-field>
    `;
  }

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

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

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

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

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

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

  private async geheimnisStandLaden(id: number): Promise<void> {
    try {
      this.geheimnisStand = await ladeGeheimnisse(id);
    } catch {
      this.geheimnisStand = []; // ohne lokalen Zugriff nicht abrufbar — dann eben leer
    }
  }

  /** Speichert nur die tatsächlich eingegebenen Werte; leere bleiben unangetastet. */
  private async geheimnisseSpeichern(id: number): Promise<void> {
    const bedarf = this.software?.benoetigteGeheimnisse ?? [];
    if (bedarf.length === 0) {
      return;
    }
    await speichereGeheimnisse(
      id,
      bedarf.map((eintrag) => ({ name: eintrag.name, wert: this.geheimwerte[eintrag.name] ?? '' }))
    );
  }

  private beiEinstufung(ereignis: CustomEvent<{ selectedOption: { dataId: string } }>): void {
    this.einstufung = ereignis.detail.selectedOption.dataId as Einstufung;
  }

  private beiProgrammart(ereignis: CustomEvent<{ selectedOption: { dataId: string } }>): void {
    this.programmart = ereignis.detail.selectedOption.dataId as Programmart;
  }

  private beiHinweis(ereignis: CustomEvent<string>): void {
    this.hinweis = ereignis.detail;
  }

  private beiBeschreibung(ereignis: CustomEvent<string>): void {
    this.beschreibung = ereignis.detail;
  }

  private beiDatei(ereignis: Event): void {
    this.datei = (ereignis.target as HTMLInputElement).files?.[0];
  }

  private beiQuelleTyp(ereignis: CustomEvent<{ selectedOption: { dataId: string } }>): void {
    const gewaehlt = ereignis.detail.selectedOption.dataId;
    this.quelleTyp = gewaehlt === OHNE_QUELLE ? '' : (gewaehlt as QuelleTyp);
  }

  private beiEltern(ereignis: CustomEvent<{ selectedOption: { dataId: string } }>): void {
    this.elternId = Number(ereignis.detail.selectedOption.dataId) || 0;
  }

  private beiAbbrechen(): void {
    this.abgebrochen.dispatch();
  }

  private async beiSpeichern(): Promise<void> {
    if (!this.name.trim()) {
      this.fehler = 'Name ist erforderlich.';
      return;
    }
    if (this.quelleTyp && this.quelleTyp !== 'manuell' && !this.quelleWert.trim()) {
      this.fehler = 'Für die gewählte Quelle fehlt die Angabe.';
      return;
    }
    if (this.quelleTyp === 'manuell' && !this.datei && !this.software?.dateiname) {
      this.fehler = 'Bitte eine Datei auswählen.';
      return;
    }
    this.speichert = true;
    this.fehler = '';
    const eingabe: SoftwareEingabe = {
      name: this.name.trim(),
      beschreibung: this.beschreibung.trim(),
      elternId: this.elternId,
      quelleTyp: this.quelleTyp,
      quelleWert: this.quelleTyp === 'manuell' ? '' : this.quelleWert.trim(),
      bauBefehl: this.quelleTyp === 'git' ? this.bauBefehl.trim() : '',
      bauErgebnis: this.quelleTyp === 'git' ? this.bauErgebnis.trim() : '',
      einstufung: this.einstufung,
      programmart: this.programmart,
      hinweis: this.hinweis.trim(),
      anleitungUrl: this.anleitungUrl.trim(),
      erwarteterHerausgeber: this.erwarteterHerausgeber.trim(),
    };
    try {
      const gespeicherteSoftware = this.software
        ? await softwareAktualisieren(this.software.id, eingabe)
        : await softwareAnlegen(eingabe);
      if (this.datei) {
        await dateiHochladen(gespeicherteSoftware.id, this.datei);
      }
      await this.geheimnisseSpeichern(gespeicherteSoftware.id);
      this.gespeichert.dispatch();
    } catch (fehler) {
      this.fehler = fehler instanceof Error ? fehler.message : 'Speichern fehlgeschlagen.';
    } finally {
      this.speichert = false;
    }
  }
}

declare global {
  interface HTMLElementTagNameMap {
    'bibliothek-formular': BibliothekFormular;
  }
}
