/**
 * User-editable preferences. Backs the Settings modal in the UI.
 *
 * Two knobs today:
 *   - activeSources       — which MCP/file/chat sources the now/check agent
 *                           is allowed to consult. Mirrors check-sources.ts.
 *   - concurrentCapPerHour — soft circuit breaker on the background scheduler.
 *                            Set high to effectively disable.
 *
 * File: MEMORY_DIR/settings.json. Read on every access (cheap; small file)
 * so edits take effect without a server restart.
 */
import { existsSync, readFileSync, writeFileSync, statSync, chmodSync, mkdirSync } from "node:fs";
import { dirname } from "node:path";
import { homedir } from "node:os";
import { join } from "node:path";
import type { Hono } from "hono";
import { SETTINGS_FILE, ICLOUD_ROOT } from "./paths";
import type { SourceId } from "./check-sources";
import { ALL_SOURCES, setActiveSourcesReader } from "./check-sources";

// Wire check-sources to read active sources from us — breaks what would
// otherwise be a startup cycle (check-sources is imported by the agent
// prompt before this module finishes evaluating in some load orders).
setActiveSourcesReader(() => loadSettings().activeSources);

export type AuthMode = "oauth" | "apiKey";

export type Settings = {
  activeSources: SourceId[];
  concurrentCapPerHour: number;
  /** Folder the file tree, NowDashboard, and chat default-cwd read from.
   *  Defaults to iCloud claude-output if it exists, else ~/Documents/head-of-product. */
  workspaceRoot: string;
  /** Which auth path the Agent SDK uses. OAuth = `claude login` credentials
   *  in ~/.claude/. apiKey = ANTHROPIC_API_KEY env var (or `apiKey` field). */
  authMode: AuthMode;
  /** Only set when authMode === "apiKey". Persisted in plain settings.json
   *  for the alpha; will move to OS keychain next sprint. */
  apiKey?: string;
  /** Setup wizard marks this true on completion. Cleared by "Re-run setup". */
  setupComplete: boolean;
};

function defaultWorkspaceRoot(): string {
  try {
    if (existsSync(ICLOUD_ROOT) && statSync(ICLOUD_ROOT).isDirectory()) {
      return ICLOUD_ROOT;
    }
  } catch { /* fall through */ }
  return join(homedir(), "Documents", "head-of-product");
}

const DEFAULTS: Settings = {
  activeSources: ["linear", "notion", "files", "chats"],
  concurrentCapPerHour: 999,
  workspaceRoot: defaultWorkspaceRoot(),
  authMode: "oauth",
  setupComplete: false,
};

const ALL_IDS = Object.keys(ALL_SOURCES) as SourceId[];

function sanitize(input: Partial<Settings> | null | undefined): Settings {
  const raw = input ?? {};
  const sources = Array.isArray(raw.activeSources) ? raw.activeSources : DEFAULTS.activeSources;
  const activeSources = sources.filter((id): id is SourceId => ALL_IDS.includes(id as SourceId));
  const cap = typeof raw.concurrentCapPerHour === "number" && isFinite(raw.concurrentCapPerHour)
    ? Math.max(0, Math.floor(raw.concurrentCapPerHour))
    : DEFAULTS.concurrentCapPerHour;
  const workspaceRoot = typeof raw.workspaceRoot === "string" && raw.workspaceRoot.trim()
    ? raw.workspaceRoot
    : DEFAULTS.workspaceRoot;
  const authMode: AuthMode = raw.authMode === "apiKey" ? "apiKey" : "oauth";
  const apiKey = typeof raw.apiKey === "string" && raw.apiKey.trim() ? raw.apiKey : undefined;
  const setupComplete = raw.setupComplete === true;
  return { activeSources, concurrentCapPerHour: cap, workspaceRoot, authMode, apiKey, setupComplete };
}

export function loadSettings(): Settings {
  if (!existsSync(SETTINGS_FILE)) return { ...DEFAULTS };
  try {
    const parsed = JSON.parse(readFileSync(SETTINGS_FILE, "utf8"));
    return sanitize(parsed);
  } catch (err) {
    console.error("[settings] failed to read; using defaults:", err);
    return { ...DEFAULTS };
  }
}

export function saveSettings(next: Partial<Settings>): Settings {
  const merged = sanitize({ ...loadSettings(), ...next });
  // Ensure MEMORY_DIR exists on a fresh Mac before first write.
  try { mkdirSync(dirname(SETTINGS_FILE), { recursive: true }); } catch { /* ignore */ }
  writeFileSync(SETTINGS_FILE, JSON.stringify(merged, null, 2));
  // 0600: settings.json may contain `apiKey`. Owner-only read/write.
  try { chmodSync(SETTINGS_FILE, 0o600); } catch { /* best effort */ }
  // Propagate API key choice into env so the Agent SDK picks it up on
  // every subsequent query() without a server restart.
  applyAuthToEnv(merged);
  return merged;
}

/** Sync settings.authMode + settings.apiKey to process.env.ANTHROPIC_API_KEY.
 *  - apiKey mode: set env var to the stored key (overrides any pre-existing).
 *  - oauth mode: clear env var so SDK falls back to ~/.claude credentials. */
export function applyAuthToEnv(s: Settings = loadSettings()) {
  if (s.authMode === "apiKey" && s.apiKey) {
    process.env.ANTHROPIC_API_KEY = s.apiKey;
  } else if (s.authMode === "oauth") {
    // Only unset if we set it earlier — don't trample an env var the
    // user explicitly exported from their shell.
    if (process.env.ANTHROPIC_API_KEY && process.env.ANTHROPIC_API_KEY === s.apiKey) {
      delete process.env.ANTHROPIC_API_KEY;
    }
  }
}

/** Live workspace root for file tree, dashboard, chat default cwd.
 *  Single read on each call — settings.json is small. */
export function getWorkspaceRoot(): string {
  return loadSettings().workspaceRoot;
}

export function registerSettingsRoutes(app: Hono) {
  app.get("/api/settings", (c) =>
    c.json({
      settings: loadSettings(),
      sources: ALL_IDS.map((id) => ({ id, label: ALL_SOURCES[id].label })),
    }),
  );

  app.put("/api/settings", async (c) => {
    const body = await c.req.json().catch(() => ({}));
    const saved = saveSettings(body);
    return c.json({ settings: saved });
  });
}
