/**
 * AI "beautify" helper.
 *
 * Turns a user's raw text into structured HTML documentation with embedded
 * Mermaid diagram blocks.
 *
 * Uses the **same model pi uses**: GLM-5.2 via the z.ai coding endpoint
 * (`https://api.z.ai/api/coding/paas/v4`), an OpenAI-compatible Chat
 * Completions API, with a 1,000,000-token context window and 131,072 max
 * output tokens. See `~/.pi/agent/models.json` (provider `zai-coding`) for the
 * canonical configuration this mirrors.
 *
 * The API key is resolved the same way pi resolves it: from
 * `~/.pi/agent/auth.json` under `zai.key`. It can be overridden with the
 * `ZAI_API_KEY` env var (useful for CI / containers). Other aspects are
 * overridable too:
 *   - ZAI_BASE_URL   (defaults to the z.ai coding v4 endpoint)
 *   - ZAI_MODEL      (defaults to `glm-5.2`)
 *   - ZAI_MAX_TOKENS (defaults to 131072, matching pi's maxTokens for glm-5.2)
 *
 * When no key is available, `beautify` throws `AiNotConfiguredError` so the
 * route layer can return a clear 503 instead of a generic failure.
 */

import { readFileSync } from 'node:fs';
import { homedir } from 'node:os';
import path from 'node:path';

const DEFAULT_BASE_URL = 'https://api.z.ai/api/coding/paas/v4';
const DEFAULT_MODEL = 'glm-5.2';
const DEFAULT_MAX_TOKENS = 131_072;

/** GLM-5.2 supports a 1,000,000-token context window (pi: contextWindow: 1000000). */
export const GLM_CONTEXT_WINDOW = 1_000_000;

export class AiNotConfiguredError extends Error {
  constructor() {
    super(
      'AI beautify is not configured — add a key to ~/.pi/agent/auth.json (zai.key) or set ZAI_API_KEY',
    );
    this.name = 'AiNotConfiguredError';
  }
}

/**
 * Resolve the z.ai API key the same way pi does: prefer the explicit env var,
 * then fall back to `~/.pi/agent/auth.json` → `zai.key`.
 */
function resolveApiKey(): string | undefined {
  if (process.env.ZAI_API_KEY) return process.env.ZAI_API_KEY;
  try {
    const authPath = path.join(homedir(), '.pi', 'agent', 'auth.json');
    const raw = readFileSync(authPath, 'utf8');
    const parsed = JSON.parse(raw) as { zai?: { key?: string } };
    return parsed.zai?.key;
  } catch {
    return undefined;
  }
}

export function isAiConfigured(): boolean {
  return Boolean(resolveApiKey());
}

const SYSTEM_PROMPT = `You are a technical documentation writer for VOID::NAV, a single-player
narrative-driven space exploration RPG. Convert the user's rough notes into polished,
well-structured HTML documentation that fits a dark, monospace-accented sci-fi UI.

Rules:
- Return a SINGLE HTML fragment (no <html>, <head>, <body>, or <script>).
- Use semantic tags: <h1> (once, as the page title), <h2>, <h3>, <p>, <ul>, <ol>, <li>,
  <table>, <thead>, <tbody>, <tr>, <th>, <td>, <blockquote>, <code>, <pre>, <strong>, <em>.
- Do NOT add classes or inline styles; the host app provides styling.
- For any flow / architecture / sequence / relationship, include a Mermaid diagram
  inside a <div class="mermaid"> ... </div> block using valid Mermaid syntax
  (flowchart, sequenceDiagram, classDiagram, erDiagram, stateDiagram-v2, etc.).
- Be concise and information-dense. Preserve all factual content from the user's input.
- If the input is ambiguous, make reasonable, evocative choices consistent with a space RPG.
- Output ONLY the HTML fragment, nothing else.`;

interface BeautifyResult {
  html: string;
  model: string;
}

export async function beautify(rawText: string): Promise<BeautifyResult> {
  const apiKey = resolveApiKey();
  if (!apiKey) throw new AiNotConfiguredError();

  const baseUrl = (process.env.ZAI_BASE_URL ?? DEFAULT_BASE_URL).replace(/\/$/, '');
  const model = process.env.ZAI_MODEL ?? DEFAULT_MODEL;
  const maxTokens = Number(process.env.ZAI_MAX_TOKENS ?? DEFAULT_MAX_TOKENS);

  const res = await fetch(`${baseUrl}/chat/completions`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${apiKey}`,
    },
    body: JSON.stringify({
      model,
      // Mirror pi's defaults for glm-5.2: high thinking, 1M context, 131072 max output.
      thinking: { type: 'enabled' },
      max_tokens: maxTokens,
      temperature: 0.5,
      messages: [
        { role: 'system', content: SYSTEM_PROMPT },
        { role: 'user', content: rawText },
      ],
    }),
  });

  if (!res.ok) {
    const detail = await res.text().catch(() => res.statusText);
    throw new Error(`AI provider error ${res.status}: ${detail}`);
  }

  const data = (await res.json()) as {
    choices?: { message?: { content?: string } }[];
  };
  const html = data.choices?.[0]?.message?.content?.trim() ?? '';
  if (!html) throw new Error('AI provider returned an empty response');
  return { html, model };
}
