/**
 * Hook artifact generator — produces *additional* opt-in helper hooks
 * (stuck-loop guard, cost-limit guard) for a project's `.claude/hooks/`
 * directory. These are NOT replacements for the DashClaw governance hooks
 * (`dashclaw_pretool.py` / `dashclaw_posttool.py` / `dashclaw_stop.py`) — they
 * sit alongside.
 *
 * Output flows through the Optimal Files manifest mechanism (Phase 6); the
 * generator never writes to disk itself.
 *
 * Ported from AgentLens (`src/hooks-gen.js`) — CommonJS → ESM. Pure; uses
 * only `node:path` for cross-platform suggested-path strings (no fs).
 */

import path from 'node:path';

export const STUCK_LOOP_DEFAULT_THRESHOLD = 5;
export const COST_LIMIT_DEFAULT_MULTIPLIER = 3;
export const COST_LIMIT_DEFAULT_FLOOR_USD = 2.0;

function validateKind(kind) {
  if (kind !== 'stuck-loop' && kind !== 'cost-limit') {
    throw new Error(`unsupported hook kind: ${kind}`);
  }
}

export function safeFilename(kind) {
  validateKind(kind);
  return `dashclaw-${kind}-guard.py`;
}

export function renderStuckLoopGuard({ threshold = STUCK_LOOP_DEFAULT_THRESHOLD, sessionId = null, projectSlug = null } = {}) {
  const t = Number.isFinite(threshold) && threshold >= 2 ? Math.floor(threshold) : STUCK_LOOP_DEFAULT_THRESHOLD;
  return [
    `#!/usr/bin/env python3`,
    `"""Auto-generated by DashClaw Code Sessions. Stuck-loop guard hook for Claude Code.`,
    ``,
    `Project: ${projectSlug || '(unknown)'}`,
    `Threshold: ${t} consecutive identical tool calls`,
    `Source session: ${sessionId || '(none)'}`,
    ``,
    `Install: register this script as a PreToolUse hook in settings.json.`,
    `See the install snippet returned alongside this file.`,
    `"""`,
    `import json, sys, time, pathlib, traceback`,
    ``,
    `THRESHOLD = ${t}`,
    `STATE_DIR = pathlib.Path.home() / ".claude-dashclaw"`,
    `STATE_DIR.mkdir(exist_ok=True)`,
    ``,
    `def _state_path(session_id):`,
    `    safe = "".join(c for c in (session_id or "default") if c.isalnum() or c in "-_") or "default"`,
    `    return STATE_DIR / f"stuck-loop-{safe}.json"`,
    ``,
    `def main():`,
    `    raw = sys.stdin.read() or "{}"`,
    `    try:`,
    `        event = json.loads(raw)`,
    `    except Exception:`,
    `        return 0  # fail-open on malformed input`,
    `    tool_name = event.get("tool_name") or event.get("tool", {}).get("name")`,
    `    session_id = event.get("session_id") or event.get("sessionId") or "default"`,
    `    if not tool_name:`,
    `        return 0`,
    `    state_file = _state_path(session_id)`,
    `    try:`,
    `        state = json.loads(state_file.read_text()) if state_file.exists() else {}`,
    `    except Exception:`,
    `        state = {}`,
    `    if state.get("last") == tool_name:`,
    `        state["count"] = int(state.get("count", 1)) + 1`,
    `    else:`,
    `        state["last"] = tool_name`,
    `        state["count"] = 1`,
    `    state["updated"] = time.time()`,
    `    try:`,
    `        state_file.write_text(json.dumps(state))`,
    `    except Exception:`,
    `        pass`,
    `    if state["count"] >= THRESHOLD:`,
    `        sys.stderr.write(`,
    `            f"dashclaw stuck-loop guard: '{tool_name}' has run {state['count']} times in a row. "`,
    `            f"Threshold {THRESHOLD} reached. Consider tightening the goal or checkpointing a plan.\\n"`,
    `        )`,
    `        try: state_file.unlink()`,
    `        except Exception: pass`,
    `        return 2  # exit code 2 -> Claude treats stderr as a hard error`,
    `    return 0`,
    ``,
    `if __name__ == "__main__":`,
    `    try:`,
    `        sys.exit(main())`,
    `    except Exception:`,
    `        traceback.print_exc(file=sys.stderr)`,
    `        sys.exit(0)`,
    ``,
  ].join('\n');
}

export function renderCostLimitGuard({ thresholdUsd, sessionId = null, projectSlug = null, projectMedianUsd = null } = {}) {
  const t = Number.isFinite(thresholdUsd) && thresholdUsd > 0
    ? Number(thresholdUsd)
    : (Number.isFinite(projectMedianUsd) && projectMedianUsd > 0
      ? Math.max(COST_LIMIT_DEFAULT_FLOOR_USD, projectMedianUsd * COST_LIMIT_DEFAULT_MULTIPLIER)
      : COST_LIMIT_DEFAULT_FLOOR_USD * COST_LIMIT_DEFAULT_MULTIPLIER);
  return [
    `#!/usr/bin/env python3`,
    `"""Auto-generated by DashClaw Code Sessions. Cost-limit guard hook for Claude Code.`,
    ``,
    `Project: ${projectSlug || '(unknown)'}`,
    `Limit: $${t.toFixed(2)} per session${Number.isFinite(projectMedianUsd) ? ` (~${COST_LIMIT_DEFAULT_MULTIPLIER}x project median $${Number(projectMedianUsd).toFixed(2)})` : ''}`,
    `Source session: ${sessionId || '(none)'}`,
    ``,
    `Install: register this script as a PostToolUse hook in settings.json.`,
    `"""`,
    `import json, sys, time, pathlib, traceback`,
    ``,
    `LIMIT_USD = ${t.toFixed(4)}`,
    `STATE_DIR = pathlib.Path.home() / ".claude-dashclaw"`,
    `STATE_DIR.mkdir(exist_ok=True)`,
    ``,
    `# Pricing per 1M tokens. Mirror of app/lib/claude-code/pricing.js — update both together.`,
    `PRICES = {`,
    `    "claude-opus-4-7":   {"input": 15.0, "output": 75.0, "cache_write": 18.75, "cache_read": 1.5},`,
    `    "claude-sonnet-4-6": {"input":  3.0, "output": 15.0, "cache_write":  3.75, "cache_read": 0.3},`,
    `    "claude-haiku-4-5":  {"input":  1.0, "output":  5.0, "cache_write":  1.25, "cache_read": 0.1},`,
    `}`,
    `DEFAULT_PRICE = {"input": 3.0, "output": 15.0, "cache_write": 3.75, "cache_read": 0.3}`,
    ``,
    `def _price_for(model):`,
    `    if not model: return DEFAULT_PRICE`,
    `    m = model.split("[")[0]`,
    `    return PRICES.get(m, DEFAULT_PRICE)`,
    ``,
    `def _state_path(session_id):`,
    `    safe = "".join(c for c in (session_id or "default") if c.isalnum() or c in "-_") or "default"`,
    `    return STATE_DIR / f"cost-limit-{safe}.json"`,
    ``,
    `def main():`,
    `    raw = sys.stdin.read() or "{}"`,
    `    try:`,
    `        event = json.loads(raw)`,
    `    except Exception:`,
    `        return 0`,
    `    usage = event.get("usage") or event.get("response", {}).get("usage") or {}`,
    `    model = event.get("model") or event.get("response", {}).get("model")`,
    `    if not usage:`,
    `        return 0`,
    `    p = _price_for(model)`,
    `    cost = (`,
    `        (usage.get("input_tokens", 0) or 0)            * p["input"]`,
    `      + (usage.get("output_tokens", 0) or 0)           * p["output"]`,
    `      + (usage.get("cache_creation_input_tokens", 0) or 0) * p["cache_write"]`,
    `      + (usage.get("cache_read_input_tokens", 0) or 0)     * p["cache_read"]`,
    `    ) / 1_000_000.0`,
    `    session_id = event.get("session_id") or event.get("sessionId") or "default"`,
    `    state_file = _state_path(session_id)`,
    `    try:`,
    `        state = json.loads(state_file.read_text()) if state_file.exists() else {"total": 0.0}`,
    `    except Exception:`,
    `        state = {"total": 0.0}`,
    `    state["total"] = float(state.get("total", 0.0)) + cost`,
    `    state["updated"] = time.time()`,
    `    try:`,
    `        state_file.write_text(json.dumps(state))`,
    `    except Exception:`,
    `        pass`,
    `    if state["total"] >= LIMIT_USD:`,
    `        sys.stderr.write(`,
    `            f"dashclaw cost-limit guard: session cost is ${'$'}{state['total']:.2f} which exceeds the limit of ${'$'}{LIMIT_USD:.2f}. "`,
    `            f"Stop, summarize what you have, and restart with a tighter goal.\\n"`,
    `        )`,
    `        return 2`,
    `    return 0`,
    ``,
    `if __name__ == "__main__":`,
    `    try:`,
    `        sys.exit(main())`,
    `    except Exception:`,
    `        traceback.print_exc(file=sys.stderr)`,
    `        sys.exit(0)`,
    ``,
  ].join('\n');
}

export function renderInstallInstructions({ kind, scriptPath, settingsScope = 'project' }) {
  validateKind(kind);
  const hookEvent = kind === 'stuck-loop' ? 'PreToolUse' : 'PostToolUse';
  const snippet = {
    hooks: {
      [hookEvent]: [
        {
          matcher: '*',
          hooks: [
            { type: 'command', command: `python "${scriptPath}"` },
          ],
        },
      ],
    },
  };
  const settingsFile = settingsScope === 'user'
    ? '~/.claude/settings.json'
    : '.claude/settings.json (inside this project)';
  return {
    hookEvent,
    settingsFile,
    snippet,
    snippetJson: JSON.stringify(snippet, null, 2),
    steps: [
      `1. Save the script to: ${scriptPath}`,
      `2. Mark it executable on macOS/Linux:  chmod +x "${scriptPath}"`,
      `3. Add the ${hookEvent} hook block below to ${settingsFile} (merge with any existing hooks).`,
      `4. Open a new Claude Code session in this project — the guard is now active.`,
    ],
  };
}

export function generateHook({ kind, threshold, thresholdUsd, sessionId, projectSlug, projectCwd, projectMedianUsd }) {
  validateKind(kind);
  const filename = safeFilename(kind);
  let content;
  if (kind === 'stuck-loop') {
    content = renderStuckLoopGuard({ threshold, sessionId, projectSlug });
  } else {
    content = renderCostLimitGuard({ thresholdUsd, sessionId, projectSlug, projectMedianUsd });
  }
  const relativePath = path.join('.claude', 'hooks', filename);
  const absolutePath = projectCwd ? path.join(projectCwd, '.claude', 'hooks', filename) : null;
  const install = renderInstallInstructions({ kind, scriptPath: absolutePath || relativePath });
  return {
    kind,
    filename,
    relativePath,
    suggestedPath: absolutePath,
    content,
    install,
  };
}
