// TagAlly Engine — KI-Alt-Texte: verkleinert verknüpfte Bilder lokal (macOS
// `sips`, unterstützt JPEG/PNG/TIFF/PSD …) und holt vom Vision-Modell einen
// Alternativtext-Vorschlag. Das Panel zeigt die Vorschläge zur Bestätigung —
// geschrieben wird erst nach menschlicher Freigabe.
//
// Konfiguration in engine/config.json:
//   "altText": {
//     "provider": "anthropic",
//     "apiKey": "sk-ant-…",              ← oder Umgebungsvariable ANTHROPIC_API_KEY
//     "model": "claude-haiku-4-5-20251001",
//     "prompt": ""                        ← optional: eigener Prompt
//   }

const fs = require("fs");
const os = require("os");
const path = require("path");
const { execFile } = require("child_process");
const { loadConfig } = require("./config.js");

function apiKey() {
  const cfg = loadConfig().altText || {};
  return cfg.apiKey || process.env.ANTHROPIC_API_KEY || "";
}

function openRouterKey() {
  const cfg = loadConfig().altText || {};
  return cfg.openRouterKey || process.env.OPENROUTER_API_KEY || "";
}

function openAiKey() {
  const cfg = loadConfig().altText || {};
  return cfg.openAiKey || process.env.OPENAI_API_KEY || "";
}

function isConfigured() {
  return !!(apiKey() || openAiKey() || openRouterKey());
}

// Bild nach JPEG konvertieren und auf max. 896 px verkleinern (längste Kante).
// Plattformneutral über das Java-Werkzeug TagAllyImg (ImageIO: JPEG/PNG/TIFF/
// BMP/GIF); nur wenn das Format dort unbekannt ist (z. B. PSD), fällt macOS
// auf `sips` zurück. Windows kennt kein sips — dort bleibt es beim Java-Weg.
const PP_DIR = path.join(__dirname, "postprocess");

function toJpegJava(src, out) {
  return new Promise((resolve, reject) => {
    execFile("java", ["-cp", PP_DIR, "TagAllyImg", src, out], (err, stdout, stderr) => {
      if (err) reject(new Error(String(stderr || err.message).trim()));
      else resolve();
    });
  });
}

function toJpegSips(src, out) {
  return new Promise((resolve, reject) => {
    execFile("sips", ["-s", "format", "jpeg", "-Z", "896", src, "--out", out], (err, stdout, stderr) => {
      if (err) reject(new Error("Bild-Konvertierung fehlgeschlagen (sips): " + (stderr || err.message).trim()));
      else resolve();
    });
  });
}

async function toJpeg(src, out) {
  try {
    await toJpegJava(src, out);
  } catch (e) {
    if (process.platform === "darwin") return toJpegSips(src, out);
    throw new Error("Bild-Konvertierung fehlgeschlagen: " + e.message
      + " — unterstützt sind JPEG, PNG, TIFF, BMP und GIF (verknüpfte PSD-Dateien bitte als TIFF/PNG ablegen).");
  }
}

// Provider-Fehler in verständliche Meldungen übersetzen (Überlastung,
// verschwundenes Modell, ungültiger Schlüssel).
function mapProviderError(provider, model, status, text) {
  if (status === 429 || /rate.?limit/i.test(text)) {
    return new Error("Modell überlastet (Rate-Limit): „" + model + "“ — kurz warten und erneut versuchen, oder im KI-Zugang ein anderes Modell wählen.");
  }
  if (status === 404 || /unavailable|not found|no endpoints/i.test(text)) {
    return new Error("Modell nicht (mehr) verfügbar: „" + model + "“ — im KI-Zugang ein anderes Modell wählen.");
  }
  if (status === 401 || status === 403) {
    return new Error(provider + "-Schlüssel ungültig oder abgelaufen (" + status + ") — im KI-Zugang neu eintragen.");
  }
  return new Error(provider + "-Anfrage fehlgeschlagen (" + status + "): " + text.slice(0, 300));
}

const PROMPT_DE =
  "Du schreibst Alternativtexte für ein barrierefreies PDF (PDF/UA). " +
  "Beschreibe den Bildinhalt prägnant in 1–2 Sätzen auf Deutsch. " +
  "Keine Einleitungen wie „Bild von“, „Foto zeigt“ oder „Das Bild“. " +
  "Enthält das Bild wesentlichen Text, gib ihn wieder. " +
  "Antworte NUR mit dem Alternativtext, ohne Anführungszeichen.";

// Gratis-Rückfall ohne API-Schlüssel: lokales Vision-Modell über Ollama
// (ollama.com — läuft komplett offline auf diesem Rechner).
const OLLAMA_URL = "http://127.0.0.1:11434";

async function ollamaAvailable() {
  try {
    const r = await fetch(OLLAMA_URL + "/api/tags", { signal: AbortSignal.timeout(1500) });
    return r.ok;
  } catch (e) {
    return false;
  }
}

async function suggestViaOllama(data, cfg) {
  const model = cfg.ollamaModel || "llava";
  const resp = await fetch(OLLAMA_URL + "/api/generate", {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({ model, prompt: cfg.prompt || PROMPT_DE, images: [data], stream: false })
  });
  if (!resp.ok) {
    const errText = await resp.text();
    if (/not found/i.test(errText)) {
      throw new Error("Ollama: Modell „" + model + "“ nicht installiert — `ollama pull " + model + "` ausführen.");
    }
    throw mapProviderError("Ollama", model, resp.status, errText);
  }
  const json = await resp.json();
  const alt = String(json.response || "").trim();
  if (!alt) throw new Error("Leere Antwort vom lokalen Modell.");
  return alt;
}

// OpenRouter: ein Schlüssel für viele Modelle (openrouter.ai), inkl. kostenloser
// „:free“-Vision-Modelle. OpenAI-kompatible Chat-Completions-API.
async function suggestViaOpenRouter(data, cfg, key) {
  const model = cfg.openRouterModel || "qwen/qwen2.5-vl-72b-instruct:free";
  const resp = await fetch("https://openrouter.ai/api/v1/chat/completions", {
    method: "POST",
    headers: { "content-type": "application/json", "authorization": "Bearer " + key },
    body: JSON.stringify({
      model,
      max_tokens: 300,
      messages: [{
        role: "user",
        content: [
          { type: "image_url", image_url: { url: "data:image/jpeg;base64," + data } },
          { type: "text", text: cfg.prompt || PROMPT_DE }
        ]
      }]
    })
  });
  if (!resp.ok) {
    const errText = await resp.text();
    throw mapProviderError("OpenRouter", model, resp.status, errText);
  }
  const json = await resp.json();
  const alt = String((json.choices && json.choices[0] && json.choices[0].message && json.choices[0].message.content) || "").trim();
  if (!alt) throw new Error("Leere Antwort vom Modell (OpenRouter).");
  return alt;
}

// OpenAI: gleiche Chat-Completions-Form wie OpenRouter, anderer Endpunkt.
async function suggestViaOpenAI(data, cfg, key) {
  const model = cfg.openAiModel || "gpt-4o-mini";
  const resp = await fetch("https://api.openai.com/v1/chat/completions", {
    method: "POST",
    headers: { "content-type": "application/json", "authorization": "Bearer " + key },
    body: JSON.stringify({
      model,
      max_tokens: 300,
      messages: [{
        role: "user",
        content: [
          { type: "image_url", image_url: { url: "data:image/jpeg;base64," + data } },
          { type: "text", text: cfg.prompt || PROMPT_DE }
        ]
      }]
    })
  });
  if (!resp.ok) {
    const errText = await resp.text();
    throw mapProviderError("OpenAI", model, resp.status, errText);
  }
  const json = await resp.json();
  const alt = String((json.choices && json.choices[0] && json.choices[0].message && json.choices[0].message.content) || "").trim();
  if (!alt) throw new Error("Leere Antwort vom Modell (OpenAI).");
  return alt;
}

async function suggestAlt(imagePath) {
  if (!fs.existsSync(imagePath)) throw new Error("Bilddatei nicht gefunden: " + imagePath);
  const cfg = loadConfig().altText || {};
  const key = apiKey();
  const oaKey = openAiKey();
  const orKey = openRouterKey();
  const model = cfg.model || "claude-haiku-4-5-20251001";

  const tmp = path.join(os.tmpdir(), "tagally-alt-" + process.pid + "-" + Date.now() + ".jpg");
  await toJpeg(imagePath, tmp);
  const data = fs.readFileSync(tmp).toString("base64");
  try { fs.unlinkSync(tmp); } catch (e) { /* Temp-Datei ist egal */ }

  // Kette: Anthropic → OpenAI → OpenRouter → lokales Ollama → Meldung.
  if (!key) {
    if (oaKey) return suggestViaOpenAI(data, cfg, oaKey);
    if (orKey) return suggestViaOpenRouter(data, cfg, orKey);
    if (await ollamaAvailable()) return suggestViaOllama(data, cfg);
    throw new Error(
      "Keine KI konfiguriert. Vier Wege (Panel → Erweitert → KI-Zugang): " +
      "(1) Anthropic-Schlüssel (console.anthropic.com), beste Qualität. " +
      "(2) OpenAI-Schlüssel (platform.openai.com). " +
      "(3) OpenRouter-Schlüssel (openrouter.ai), dort gibt es kostenlose „:free“-Vision-Modelle. " +
      "(4) Kostenlos & offline: Ollama installieren (ollama.com), `ollama pull llava`, laufen lassen."
    );
  }

  const resp = await fetch("https://api.anthropic.com/v1/messages", {
    method: "POST",
    headers: {
      "content-type": "application/json",
      "x-api-key": key,
      "anthropic-version": "2023-06-01"
    },
    body: JSON.stringify({
      model,
      max_tokens: 300,
      messages: [{
        role: "user",
        content: [
          { type: "image", source: { type: "base64", media_type: "image/jpeg", data } },
          { type: "text", text: cfg.prompt || PROMPT_DE }
        ]
      }]
    })
  });
  if (!resp.ok) {
    const errText = await resp.text();
    throw mapProviderError("Anthropic", model, resp.status, errText);
  }
  const json = await resp.json();
  const alt = (json.content || []).filter((c) => c.type === "text").map((c) => c.text).join(" ").trim();
  if (!alt) throw new Error("Leere Antwort vom Modell.");
  return alt;
}

module.exports = { suggestAlt, isConfigured };
