'use strict';
// Per-project config loader — reads `.ctm.json` from a cwd or any parent
// directory up to the filesystem root. First file found wins.
//
// Schema (all fields optional):
//   {
//     "provider": "claude-code" | "codex" | "gemini" | "shell" | ...,
//     "cmd": "claude",          // launch binary (overrides default for provider)
//     "args": ["--model", "sonnet"],  // extra launch args
//     "model": "sonnet",       // convenience: sets CLAUDE_MODEL / equivalent
//     "env": { "FOO": "bar" }, // extra env vars for the PTY
//     "hooks": {                 // same schema as .ctm-hooks.json
//       "onIdle": "...",
//       "onBusy": "...",
//       "onWaitingInput": "...",
//       "timeoutMs": 10000
//     },
//     "fallbackArgs": ["--resume"] // list of args to strip on early-exit retry
//   }
//
// Resolution walks up from cwd until it finds .ctm.json OR hits a boundary
// (root, $HOME, or a .git directory without .ctm.json). The first match is
// parsed and returned. Malformed JSON is ignored (warned, not thrown).

const fs = require('fs');
const path = require('path');
const os = require('os');

const FILE_NAME = '.ctm.json';

// Cache by directory path + mtime to avoid re-reading on every PTY spawn.
const _cache = new Map(); // absPath -> { mtime, data }

function _readJsonSafe(p) {
  try {
    const stat = fs.statSync(p);
    const cached = _cache.get(p);
    if (cached && cached.mtime === stat.mtimeMs) return cached.data;
    const data = JSON.parse(fs.readFileSync(p, 'utf8'));
    _cache.set(p, { mtime: stat.mtimeMs, data });
    return data;
  } catch (e) {
    if (e.code === 'ENOENT') return null;
    // Malformed JSON — warn once but don't crash.
    console.warn(`[project-config] ${p} parse error: ${e.message}`);
    _cache.set(p, { mtime: 0, data: null });
    return null;
  }
}

// Walk up from `startDir` until we find FILE_NAME or reach a sensible
// boundary. Returns the parsed config + the dir it was found in.
function findProjectConfig(startDir) {
  if (!startDir) return null;
  const home = os.homedir() || '';
  let dir = path.resolve(startDir);
  const seen = new Set();
  while (dir && !seen.has(dir)) {
    seen.add(dir);
    const candidate = path.join(dir, FILE_NAME);
    const data = _readJsonSafe(candidate);
    if (data && typeof data === 'object') {
      return { dir, file: candidate, data: _normalize(data) };
    }
    // Stop if we've walked above home or hit the root.
    if (dir === '/' || dir === home) break;
    const parent = path.dirname(dir);
    if (!parent || parent === dir) break;
    dir = parent;
  }
  return null;
}

function _normalize(data) {
  const out = {};
  if (typeof data.provider === 'string' && data.provider) out.provider = data.provider;
  if (typeof data.cmd === 'string' && data.cmd) out.cmd = data.cmd;
  if (Array.isArray(data.args)) out.args = data.args.filter(a => typeof a === 'string');
  if (typeof data.model === 'string' && data.model) out.model = data.model;
  if (data.env && typeof data.env === 'object' && !Array.isArray(data.env)) {
    out.env = {};
    for (const [k, v] of Object.entries(data.env)) {
      if (typeof k === 'string' && (typeof v === 'string' || typeof v === 'number')) {
        out.env[k] = String(v);
      }
    }
  }
  if (data.hooks && typeof data.hooks === 'object' && !Array.isArray(data.hooks)) {
    out.hooks = data.hooks;
  }
  if (Array.isArray(data.fallbackArgs)) {
    out.fallbackArgs = data.fallbackArgs.filter(a => typeof a === 'string');
  }
  return out;
}

// Merge a project config (cfg) onto a spawn spec. Project config wins for
// present keys; callers provide the spec that would otherwise be used.
//   spec: { cmd, args, env, model }
// Returns a new spec; does not mutate input.
function applyToSpec(cfg, spec) {
  const next = { ...spec, args: Array.isArray(spec.args) ? spec.args.slice() : [], env: { ...(spec.env || {}) } };
  if (!cfg) return next;
  if (cfg.cmd) next.cmd = cfg.cmd;
  if (cfg.args && cfg.args.length) {
    // Project args APPEND to the caller-supplied args so callers can still
    // pass --resume <id> when that's the intent.
    next.args = next.args.concat(cfg.args);
  }
  if (cfg.model) next.model = cfg.model;
  if (cfg.env) next.env = { ...next.env, ...cfg.env };
  return next;
}

function _clearCache() { _cache.clear(); }

module.exports = {
  FILE_NAME,
  findProjectConfig,
  applyToSpec,
  _clearCache,
};
