import { execFileSync } from 'node:child_process';
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';

import react from '@vitejs/plugin-react';
import { defineConfig } from 'electron-vite';
import type { Plugin } from 'vite';

import { CSP_PLATZHALTER, cspRichtlinie } from './src/shared/csp';

/**
 * Version aus der package.json, zur Bauzeit fest eingesetzt.
 *
 * `app.getVersion()` liefert nur im gepackten Zustand die Anwendungsversion.
 * Wird der gebaute Einstiegspunkt direkt gestartet – etwa in der Entwicklung
 * oder in den Oberflächentests – findet Electron keine package.json und gibt
 * ersatzweise die Electron-Version zurück. Das stand dann fälschlich als
 * „Programmversion" auf dem Startbildschirm.
 */
const paket = JSON.parse(readFileSync(resolve(__dirname, 'package.json'), 'utf-8')) as {
  version: string;
};

/** Ein Git-Aufruf, dessen Fehlschlag den Bau nicht aufhält. */
function git(...argumente: readonly string[]): string | null {
  try {
    return execFileSync('git', argumente, {
      cwd: __dirname,
      encoding: 'utf-8',
      stdio: ['ignore', 'pipe', 'ignore'],
    }).trim();
  } catch {
    /* Kein Git, kein Arbeitsbaum, kein Commit – etwa beim Bau aus einem
       Quellarchiv. Die Anwendung läuft ohne Baukennung genauso. */
    return null;
  }
}

/**
 * Woraus dieser Bau entstanden ist.
 *
 * Die Versionsnummer allein reicht nicht: Zwischen zwei Veröffentlichungen
 * entstehen viele Bauten mit derselben Nummer, und wer eine Rückmeldung gibt
 * („bei mir tut X nicht“), kann sonst nicht sagen, welchen Stand er vor sich
 * hat.
 *
 * Genommen wird das **Commit-Datum**, nicht das Bau-Datum: Dasselbe Commit
 * ergibt so immer dieselbe Kennung, und der Bau bleibt wiederholbar. Ein `+`
 * verrät einen Arbeitsbaum mit ungesicherten Änderungen – dann stimmt der
 * Quellstand eben nicht mit dem Commit überein, und das muss man sehen.
 */
function baukennung(): { kennung: string; stand: string } {
  const kurz = git('rev-parse', '--short', 'HEAD');
  if (kurz === null) {
    return { kennung: '', stand: '' };
  }
  /* `--untracked-files=no`, und das mit Absicht: electron-vite legt beim
     Bauen eine transpilierte Kopie dieser Datei daneben
     (`electron.vite.config.<zeitstempel>.mjs`) und räumt sie danach weg.
     Ohne den Schalter meldete `status` genau diese Datei – und jeder
     Bau truegänge ein `+`, auch aus einem tadellos sauberen Arbeitsbaum.
     Eine Marke, die immer leuchtet, sagt nichts mehr.

     Der Preis: Eine völlig neue, noch nicht aufgenommene Quelldatei fällt
     nicht auf. Der Fall ist selten und fällt anderswo laut auf – wer das
     Commit auscheckt, bekommt einen Bau, der gar nicht erst durchläuft. */
  const schmutzig = (git('status', '--porcelain', '--untracked-files=no') ?? '').length > 0;
  const datum = git('log', '-1', '--format=%cI') ?? '';
  return { kennung: schmutzig ? `${kurz}+` : kurz, stand: datum.slice(0, 10) };
}

const bau = baukennung();

/**
 * Ersetzt den Platzhalter im `<meta http-equiv="Content-Security-Policy">`
 * durch die Richtlinie aus `src/shared/csp.ts`.
 *
 * Im Dev-Modus braucht Vite `'unsafe-inline'` (HMR-Präambel, injizierte
 * Styles) – im Produktionsbuild gilt die strenge Variante.
 *
 * `'meta'` als Ziel, und das ist hier der ganze Punkt: Direktiven, die ein
 * Browser im `<meta>`-Element ignoriert (`frame-ancestors`), bleiben draußen.
 * Sonst schreibt Chromium bei jedem Start einen Konsolenfehler über eine
 * Direktive, die ohnehin nicht gilt. Den Kopf-Weg bedient
 * `src/main/sicherheit.ts`.
 */
function cspPlugin(): Plugin {
  return {
    name: 'lernapp-csp',
    transformIndexHtml: {
      order: 'pre',
      handler(html, ctx) {
        const modus = ctx.server ? 'development' : 'production';
        return html.replaceAll(CSP_PLATZHALTER, cspRichtlinie(modus, 'meta'));
      },
    },
  };
}

export default defineConfig({
  main: {
    define: {
      __ANWENDUNGS_VERSION__: JSON.stringify(paket.version),
      __BAUKENNUNG__: JSON.stringify(bau.kennung),
      __BAUSTAND__: JSON.stringify(bau.stand),
    },
    build: {
      // Native und CommonJS-Abhängigkeiten (better-sqlite3) dürfen nicht
      // gebündelt werden – sie bleiben in node_modules und landen über
      // electron-builder im Paket. `true` ist zwar der Standard, steht hier
      // aber bewusst explizit, weil das Gegenstück im Preload abweicht.
      externalizeDeps: true,
      rollupOptions: {
        input: { index: resolve(__dirname, 'src/main/index.ts') },
      },
    },
  },

  preload: {
    build: {
      // MUSS false sein: mit `sandbox: true` kann das Preload-Skript zur
      // Laufzeit nichts nachladen, also muss alles gebündelt sein.
      // Ausgabeformat bleibt CommonJS (package.json hat kein
      // "type": "module") – sandboxed Preloads unterstützen kein ESM.
      externalizeDeps: false,
      rollupOptions: {
        input: { index: resolve(__dirname, 'src/preload/index.ts') },
      },
    },
  },

  renderer: {
    root: resolve(__dirname, 'src/renderer'),
    resolve: {
      alias: {
        '@renderer': resolve(__dirname, 'src/renderer/src'),
        '@shared': resolve(__dirname, 'src/shared'),
      },
    },
    plugins: [react(), cspPlugin()],
    build: {
      rollupOptions: {
        input: { index: resolve(__dirname, 'src/renderer/index.html') },
      },
    },
  },
});
