/**
 * 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";
// posthog is imported lazily inside the PUT handler to break the cycle
// (posthog → identity → settings → posthog). Importing it at module top
// level here caused TDZ on DEFAULTS/ALL_IDS during startup.

// 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;
  /** Stable per-install UUID. Used as the PostHog distinct ID before OAuth
   *  identity resolves (and as a permanent fallback for apiKey-mode users
   *  who don't have an Anthropic account UUID). Generated once on first save. */
  instanceId: string;
};

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,
  instanceId: "",
};

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;
  const instanceId = typeof raw.instanceId === "string" && raw.instanceId.trim()
    ? raw.instanceId
    : crypto.randomUUID();
  return { activeSources, concurrentCapPerHour: cap, workspaceRoot, authMode, apiKey, setupComplete, instanceId };
}

export function loadSettings(): Settings {
  if (!existsSync(SETTINGS_FILE)) {
    // First launch — persist defaults so the instanceId (generated in sanitize)
    // survives across restarts. Without this every restart gets a new UUID
    // and PostHog distinct-id continuity is lost.
    const seeded = sanitize(null);
    try {
      mkdirSync(dirname(SETTINGS_FILE), { recursive: true });
      writeFileSync(SETTINGS_FILE, JSON.stringify(seeded, null, 2));
      chmodSync(SETTINGS_FILE, 0o600);
    } catch { /* best effort */ }
    return seeded;
  }
  try {
    const parsed = JSON.parse(readFileSync(SETTINGS_FILE, "utf8"));
    const sanitized = sanitize(parsed);
    // If the stored file had no instanceId, sanitize() will have minted one —
    // persist it back so the same UUID is reused next launch.
    if (!parsed?.instanceId) {
      try { writeFileSync(SETTINGS_FILE, JSON.stringify(sanitized, null, 2)); } catch { /* best effort */ }
    }
    return sanitized;
  } catch (err) {
    console.error("[settings] failed to read; using defaults:", err);
    return sanitize(null);
  }
}

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);
    // Lazy import — breaks the startup import cycle (see header).
    const { posthog, DISTINCT_ID } = await import("./posthog");
    posthog.capture({
      distinctId: DISTINCT_ID,
      event: "settings saved",
      properties: {
        auth_mode: saved.authMode,
        active_sources: saved.activeSources,
        setup_complete: saved.setupComplete,
        source_count: saved.activeSources.length,
      },
    });
    return c.json({ settings: saved });
  });
}
