// TagAlly Engine — Phase 0: lokaler HTTP-Dienst für das UXP-Panel.
// Start:  node server.js   (lauscht nur auf 127.0.0.1:8474)

const http = require("http");
const fs = require("fs");
const path = require("path");
const { validate, findVerapdf, VERSION, languages } = require("./validate.js");
const { postprocess } = require("./postprocess.js");
const { suggestAlt, isConfigured } = require("./alttext.js");
const { structview } = require("./structview.js");
const { translator, detect } = require("./i18n.js");
const { configPath } = require("./config.js");

// Sprache der Konsolen-Meldungen — nicht die des Prüfprotokolls.
const tc = translator(detect());

const PORT = 8474;

function send(res, status, body) {
  res.writeHead(status, {
    "Content-Type": "application/json; charset=utf-8",
    "Access-Control-Allow-Origin": "*",
    "Access-Control-Allow-Headers": "Content-Type",
    "Access-Control-Allow-Methods": "GET, POST, OPTIONS"
  });
  res.end(JSON.stringify(body));
}

const server = http.createServer((req, res) => {
  if (req.method === "OPTIONS") return send(res, 204, {});

  if (req.method === "GET" && req.url === "/health") {
    return send(res, 200, { status: "ok", version: VERSION, verapdf: findVerapdf(), aiAlt: isConfigured(), languages: languages() });
  }

  // Sauber beenden. Braucht der Starter, um nach einem Update eine noch
  // laufende ältere Engine abzulösen — sonst belegt die alte den Port weiter
  // und der Nutzer arbeitet unbemerkt mit der Vorgängerversion.
  // Erreichbar nur von diesem Rechner: Der Server lauscht ausschließlich
  // auf 127.0.0.1 (siehe server.listen unten).
  if (req.method === "POST" && req.url === "/shutdown") {
    send(res, 200, { status: "stopping", version: VERSION });
    return setTimeout(() => {
      console.log(tc("console.stopping"));
      server.close(() => process.exit(0));
      // Falls noch Verbindungen offen hängen, nicht ewig warten.
      setTimeout(() => process.exit(0), 1500).unref();
    }, 50);
  }

  // Live-Modellliste von OpenRouter: Vision-fähige Gratis-Modelle + günstige
  // Bezahl-Alternativen (Preis in USD pro Mio. Input-Token).
  if (req.method === "GET" && req.url === "/ormodels") {
    (async () => {
      try {
        const r = await fetch("https://openrouter.ai/api/v1/models", { signal: AbortSignal.timeout(15000) });
        if (!r.ok) throw new Error("OpenRouter-Modellliste nicht erreichbar (" + r.status + ")");
        const d = await r.json();
        const free = [];
        const paid = [];
        for (const m of d.data || []) {
          const id = m.id || "";
          const mods = ((m.architecture || {}).input_modalities) || [];
          if (!mods.includes("image")) continue;
          if (/safety|guard|moderation/i.test(id)) continue; // Klassifikatoren aussortieren
          const price = parseFloat(((m.pricing || {}).prompt) || "0");
          if (id.endsWith(":free")) free.push(id);
          else if (price > 0 && price < 0.0000005) paid.push({ id, price: Math.round(price * 1e8) / 100 });
        }
        free.sort();
        paid.sort((a, b) => a.price - b.price);
        send(res, 200, { free, paid: paid.slice(0, 10) });
      } catch (e) {
        send(res, 500, { error: e.message });
      }
    })();
    return;
  }

  // Aktuelle KI-Konfiguration (ohne Schlüssel-Werte — nur ob gesetzt).
  if (req.method === "GET" && req.url === "/altconfig") {
    try {
      const cfg = JSON.parse(fs.readFileSync(configPath(), "utf8"));
      const at = cfg.altText || {};
      return send(res, 200, {
        hasApiKey: !!(at.apiKey || process.env.ANTHROPIC_API_KEY),
        hasOpenAiKey: !!(at.openAiKey || process.env.OPENAI_API_KEY),
        hasOpenRouterKey: !!(at.openRouterKey || process.env.OPENROUTER_API_KEY),
        model: at.model || "",
        openAiModel: at.openAiModel || "",
        openRouterModel: at.openRouterModel || "",
        ollamaModel: at.ollamaModel || ""
      });
    } catch (e) {
      return send(res, 500, { error: e.message });
    }
  }

  // KI-Schlüssel/Modell aus dem Panel speichern (nur übergebene Felder überschreiben).
  if (req.method === "POST" && req.url === "/altconfig") {
    let body = "";
    req.on("data", (chunk) => (body += chunk));
    req.on("end", () => {
      try {
        const { apiKey, openAiKey, openRouterKey, openRouterModel, clear } = JSON.parse(body || "{}");
        const cfgPath = configPath();
        const cfg = JSON.parse(fs.readFileSync(cfgPath, "utf8"));
        cfg.altText = cfg.altText || {};
        if (typeof apiKey === "string" && apiKey) cfg.altText.apiKey = apiKey;
        if (typeof openAiKey === "string" && openAiKey) cfg.altText.openAiKey = openAiKey;
        if (typeof openRouterKey === "string" && openRouterKey) cfg.altText.openRouterKey = openRouterKey;
        if (typeof openRouterModel === "string" && openRouterModel) cfg.altText.openRouterModel = openRouterModel;
        // Schlüssel löschen (nur die drei Key-Felder sind erlaubt).
        if (Array.isArray(clear)) {
          for (const field of clear) {
            if (["apiKey", "openAiKey", "openRouterKey"].includes(field)) cfg.altText[field] = "";
          }
        }
        fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2) + "\n");
        console.log("KI-Zugang aktualisiert (aiAlt=" + isConfigured() + ")");
        send(res, 200, { ok: true, aiAlt: isConfigured() });
      } catch (e) {
        console.error("→ Fehler: " + e.message);
        send(res, 500, { error: e.message });
      }
    });
    return;
  }

  if (req.method === "POST" && req.url === "/alttext") {
    let body = "";
    req.on("data", (chunk) => (body += chunk));
    req.on("end", async () => {
      try {
        const { file } = JSON.parse(body || "{}");
        if (!file) return send(res, 400, { error: "Feld \"file\" fehlt." });
        console.log("KI-Alt-Text: " + file);
        const alt = await suggestAlt(file);
        console.log("→ " + alt.slice(0, 80));
        send(res, 200, { alt });
      } catch (e) {
        console.error("→ Fehler: " + e.message);
        send(res, 500, { error: e.message });
      }
    });
    return;
  }

  if (req.method === "POST" && req.url === "/validate") {
    let body = "";
    req.on("data", (chunk) => (body += chunk));
    req.on("end", async () => {
      try {
        const { file, flavour, contrast, lang } = JSON.parse(body || "{}");
        if (!file) return send(res, 400, { error: "Feld \"file\" fehlt." });
        console.log("Validiere: " + file);
        const result = await validate(file, flavour || "ua1", contrast || null, lang || "de");
        console.log(result.compliant ? "→ bestanden" : `→ ${result.failedRules} Regel(n) verletzt`);
        send(res, 200, result);
      } catch (e) {
        console.error("→ Fehler: " + e.message);
        send(res, 500, { error: e.message });
      }
    });
    return;
  }

  // Struktur-Ansicht: Tag-Baum des PDFs als HTML (…-struktur.html daneben).
  if (req.method === "POST" && req.url === "/structview") {
    let body = "";
    req.on("data", (chunk) => (body += chunk));
    req.on("end", async () => {
      try {
        const { file, lang } = JSON.parse(body || "{}");
        if (!file) return send(res, 400, { error: "Feld \"file\" fehlt." });
        console.log("Struktur-Ansicht: " + file);
        const result = await structview(file, lang || "de");
        console.log("→ " + result.output);
        send(res, 200, result);
      } catch (e) {
        console.error("→ Fehler: " + e.message);
        send(res, 500, { error: e.message });
      }
    });
    return;
  }

  if (req.method === "POST" && req.url === "/postprocess") {
    let body = "";
    req.on("data", (chunk) => (body += chunk));
    req.on("end", async () => {
      try {
        const { file, langs, lang } = JSON.parse(body || "{}");
        if (!file) return send(res, 400, { error: "Feld \"file\" fehlt." });
        console.log("Post-Processing: " + file + (Array.isArray(langs) && langs.length ? " (+" + langs.length + " Sprachbereiche)" : ""));
        const result = await postprocess(file, langs, lang || "de");
        console.log("→ " + result.output);
        send(res, 200, result);
      } catch (e) {
        console.error("→ Fehler: " + e.message);
        send(res, 500, { error: e.message });
      }
    });
    return;
  }

  send(res, 404, { error: "Unbekannter Endpunkt." });
});

// EADDRINUSE kommt als Ereignis, nicht als Ausnahme. Ohne diesen Zweig endet
// der Start mit einem rohen Node-Stapelabzug, aus dem niemand schlau wird.
server.on("error", (e) => {
  if (e && e.code === "EADDRINUSE") {
    console.error(tc("console.portBusy", { port: PORT }));
    console.error(tc("console.portBusyHint"));
    process.exit(1);
  }
  throw e;
});

server.listen(PORT, "127.0.0.1", () => {
  console.log(tc("console.running", { version: VERSION, url: `http://127.0.0.1:${PORT}` }));
  const vp = findVerapdf();
  console.log(vp ? tc("console.verapdf", { path: vp }) : tc("console.verapdfMissing"));
});
