"use strict";

const fs = require("fs");
const path = require("path");
const os = require("os");
const crypto = require("crypto");
const { execSync } = require("child_process");

// ─── Data Gathering ────────────────────────────────────────────────────────

/** Read all stdin synchronously. */
function parseStdin() {
  try {
    if (process.stdin.isTTY) return {};
    const buf = fs.readFileSync(0, "utf-8");
    if (!buf || !buf.trim()) return {};
    return JSON.parse(buf);
  } catch {
    return {};
  }
}

/** Resolve session ID from env var or derive from PWD + PPID. */
function resolveSessionId() {
  if (process.env.CLAUDE_SESSION_ID) {
    return process.env.CLAUDE_SESSION_ID;
  }
  const input = `${process.cwd()}${process.ppid}`;
  return crypto.createHash("sha256").update(input).digest("hex").slice(0, 8);
}

/** Read bridge file for phase description and cached git branch. */
function readBridgeFile(sessionId) {
  const bridgePath = path.join(os.tmpdir(), `claude-ctx-${sessionId}.json`);
  try {
    if (!fs.existsSync(bridgePath)) return null;
    return JSON.parse(fs.readFileSync(bridgePath, "utf-8"));
  } catch {
    return null;
  }
}

/** Read ECK version from global-state.json. */
function readEckVersion() {
  const statePath = path.join(
    os.homedir(),
    ".claude",
    "evolv-coder-kit",
    "global-state.json",
  );
  try {
    if (!fs.existsSync(statePath)) return null;
    const data = JSON.parse(fs.readFileSync(statePath, "utf-8"));
    return data.version || null;
  } catch {
    return null;
  }
}

/**
 * Read project entry from registry.yaml by matching path to projectDir or cwd.
 * Returns { dev_rigor, project_type, learning_autonomy } or null fields.
 * Falls back to project-constants.md, then user-preferences.json.
 */
function readRegistryProject(projectDir) {
  const result = {
    devRigor: null,
    projectType: null,
    learningAutonomy: null,
    state: null,
    auditStatus: null,
    isStale: false,
  };
  const registryPath = path.join(
    os.homedir(),
    ".claude",
    "evolv-coder-kit",
    "registry.yaml",
  );

  // Try registry first
  try {
    if (fs.existsSync(registryPath)) {
      const content = fs.readFileSync(registryPath, "utf-8");
      // Verify schema_version (accept both schema_version and version for compat)
      const schemaMatch =
        content.match(/^schema_version:\s*(\d+)/m) ||
        content.match(/^version:\s*(\d+)/m);
      if (!schemaMatch) {
        // Unrecognized schema — skip registry
      } else if (projectDir) {
        // Simple YAML parsing: find project entry matching the path
        // Look for project_path: "..." that matches projectDir, then read fields after it
        const escaped = projectDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
        const pathMatch =
          content.match(new RegExp(`project_path:\\s*"?${escaped}"?`)) ||
          content.match(new RegExp(`path:\\s*"?${escaped}"?`));
        if (pathMatch) {
          // Extract the block for this project only — bounded by next sibling key or EOF
          const blockStart = Math.max(
            0,
            content.lastIndexOf("\n", pathMatch.index),
          );
          // Find next project key at same indent level (2-space indent + key:)
          const restAfterMatch = content.slice(
            pathMatch.index + pathMatch[0].length,
          );
          const nextSiblingMatch = restAfterMatch.match(
            /\n  [a-zA-Z][\w-]*:\s*\n/,
          );
          const blockEnd = nextSiblingMatch
            ? pathMatch.index + pathMatch[0].length + nextSiblingMatch.index
            : content.length;
          const projectBlock = content.slice(blockStart, blockEnd);
          const rigorMatch = projectBlock.match(/dev_rigor:\s*"?(\w+)"?/);
          const typeMatch = projectBlock.match(/project_type:\s*"?([^"\n]+)"?/);
          const learnMatch = projectBlock.match(
            /learning_autonomy:\s*"?(\w+)"?/,
          );
          const stateMatch = projectBlock.match(/state:\s*"?(\w+)"?/);
          const auditMatch = projectBlock.match(/audit_status:\s*"?(\w+)"?/);
          if (rigorMatch) result.devRigor = rigorMatch[1];
          if (typeMatch) result.projectType = typeMatch[1].trim();
          if (learnMatch) result.learningAutonomy = learnMatch[1];
          if (stateMatch) result.state = stateMatch[1];
          if (auditMatch) result.auditStatus = auditMatch[1];
          // Compute staleness from last_audit date
          const auditDateMatch = projectBlock.match(
            /last_audit:\s*\n\s+date:\s*"?([^"\n]+)"?/,
          );
          if (auditDateMatch && auditDateMatch[1] !== "null") {
            const auditDate = new Date(auditDateMatch[1]);
            const daysSince =
              (Date.now() - auditDate.getTime()) / (1000 * 60 * 60 * 24);
            result.isStale = daysSince > 7;
          }
        }
      }
    }
  } catch {
    /* ignore */
  }

  // Fallback: project-constants.md
  if (projectDir && (!result.devRigor || !result.projectType)) {
    try {
      const pcFile = path.join(projectDir, ".claude", "project-constants.md");
      if (fs.existsSync(pcFile)) {
        const content = fs.readFileSync(pcFile, "utf-8");
        if (!result.devRigor) {
          const match =
            content.match(/^\|\s*DEV_RIGOR\s*\|\s*(\w+)\s*\|/m) ||
            content.match(/^\|\s*DEV_MODE\s*\|\s*(\w+)\s*\|/m);
          if (match) {
            const valueMap = { rapid: "lite", rigorous: "strict" };
            result.devRigor = valueMap[match[1]] || match[1];
          }
        }
        if (!result.projectType) {
          const match =
            content.match(/^\|\s*PROJECT_TYPE\s*\|\s*([^\|]+)\s*\|/m) ||
            content.match(/^\|\s*PROJECT_MATURITY\s*\|\s*([^\|]+)\s*\|/m);
          if (match) {
            const val = match[1].trim();
            // Map legacy numbers to labels
            const numMap = {
              1: "PoC",
              2: "Prototype",
              3: "MVP",
              4: "Pilot",
              5: "Production",
            };
            result.projectType = numMap[val] || val;
          }
        }
        if (!result.learningAutonomy) {
          const match = content.match(
            /^\|\s*LEARNING_AUTONOMY\s*\|\s*(\w+)\s*\|/m,
          );
          if (match) result.learningAutonomy = match[1];
        }
      }
    } catch {
      /* ignore */
    }
  }

  // Fallback: user preferences
  try {
    const prefsPath = path.join(
      os.homedir(),
      ".claude",
      "evolv-coder-kit",
      "user-preferences.json",
    );
    if (fs.existsSync(prefsPath)) {
      const prefs = JSON.parse(fs.readFileSync(prefsPath, "utf-8"));
      if (!result.devRigor) {
        const rigor = prefs.devRigor || prefs.devMode;
        if (rigor) {
          const valueMap = { rapid: "lite", rigorous: "strict" };
          result.devRigor = valueMap[rigor] || rigor;
        }
      }
      if (!result.learningAutonomy && prefs.learningAutonomy) {
        result.learningAutonomy = prefs.learningAutonomy;
      }
    }
  } catch {
    /* ignore */
  }

  return {
    devRigor: result.devRigor || "standard",
    projectType: result.projectType || null,
    learningAutonomy: result.learningAutonomy || "advisor",
    state: result.state || null,
    auditStatus: result.auditStatus || null,
    isStale: result.isStale || false,
  };
}

/** Read output style from settings.json or stdin. */
function readOutputStyle(stdinStyle) {
  // settings.json takes priority
  try {
    const settingsPath = path.join(os.homedir(), ".claude", "settings.json");
    if (fs.existsSync(settingsPath)) {
      const settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
      if (settings.outputStyle) {
        return settings.outputStyle.replace(/^evolv-/, "");
      }
    }
  } catch {
    /* ignore */
  }

  if (stdinStyle) return stdinStyle.replace(/^evolv-/, "");

  // Fallback: user preferences
  try {
    const prefsPath = path.join(
      os.homedir(),
      ".claude",
      "evolv-coder-kit",
      "user-preferences.json",
    );
    if (fs.existsSync(prefsPath)) {
      const prefs = JSON.parse(fs.readFileSync(prefsPath, "utf-8"));
      if (prefs.outputMode) return prefs.outputMode.replace(/^evolv-/, "");
    }
  } catch {
    /* ignore */
  }
  return "standard";
}

/** Read protection state from global-state.json. */
function readProtection() {
  const statePath = path.join(
    os.homedir(),
    ".claude",
    "evolv-coder-kit",
    "global-state.json",
  );
  try {
    if (!fs.existsSync(statePath)) return "off";
    const data = JSON.parse(fs.readFileSync(statePath, "utf-8"));
    return data.protectionsEnabled !== false ? "on" : "off";
  } catch {
    /* ignore */
  }
  return "off";
}

/** Get git branch for a directory. */
function gitBranch(dir) {
  try {
    return (
      execSync(`git -C "${dir}" rev-parse --abbrev-ref HEAD`, {
        encoding: "utf-8",
        timeout: 3000,
        stdio: ["pipe", "pipe", "pipe"],
      }).trim() || null
    );
  } catch {
    return null;
  }
}

/** Get count of dirty (modified) files in a git repo. */
function gitDirtyCount(dir) {
  try {
    const out = execSync(`git -C "${dir}" status --porcelain`, {
      encoding: "utf-8",
      timeout: 3000,
      stdio: ["pipe", "pipe", "pipe"],
    });
    const lines = out.trim().split("\n").filter(Boolean);
    return lines.length;
  } catch {
    return 0;
  }
}

/** Resolve git toplevel for a directory (returns null if not a git repo). */
function gitToplevel(dir) {
  try {
    return (
      execSync(`git -C "${dir}" rev-parse --show-toplevel`, {
        encoding: "utf-8",
        timeout: 3000,
        stdio: ["pipe", "pipe", "pipe"],
      }).trim() || null
    );
  } catch {
    return null;
  }
}

/** Deduplicate added dirs against project dir and cwd, and within the list.
 *  Compares both exact paths AND git repo identity (toplevel) to catch
 *  symlinks, mount aliases, and multiple paths into the same repo. */
function deduplicateDirs(addedDirs, projectDir, cwd) {
  if (!addedDirs || addedDirs.length === 0) return [];
  const seenPaths = new Set();
  const seenRepos = new Set();
  for (const dir of [projectDir, cwd].filter(Boolean)) {
    seenPaths.add(dir);
    const top = gitToplevel(dir);
    if (top) seenRepos.add(top);
  }
  const result = [];
  for (const dir of addedDirs) {
    if (!dir || seenPaths.has(dir)) continue;
    const top = gitToplevel(dir);
    if (top && seenRepos.has(top)) continue;
    seenPaths.add(dir);
    if (top) seenRepos.add(top);
    result.push(dir);
  }
  return result;
}

/** Write bridge file atomically for compatibility with update-stage.sh and context-monitor. */
function writeBridge(sessionId, contextPercent, stageDesc, branch) {
  const bridgePath = path.join(os.tmpdir(), `claude-ctx-${sessionId}.json`);
  const tmpPath = bridgePath + ".tmp";
  const data = {
    contextPercent: contextPercent != null ? contextPercent : null,
    stageDescription: stageDesc || "Ready",
    gitBranch: branch || "",
    timestamp: Math.floor(Date.now() / 1000),
    sessionId,
  };
  try {
    fs.writeFileSync(tmpPath, JSON.stringify(data), "utf-8");
    fs.renameSync(tmpPath, bridgePath);
  } catch {
    /* ignore write errors */
  }
}

/** Persist session ID to project .claude/.session-id so update-stage.sh can locate the bridge file. */
function writeSessionId(sessionId, projectDir) {
  if (!sessionId) return;
  const dirs = [projectDir, process.cwd()].filter(Boolean);
  for (const dir of dirs) {
    const claudeDir = path.join(dir, ".claude");
    const idPath = path.join(claudeDir, ".session-id");
    try {
      if (!fs.existsSync(claudeDir)) continue;
      fs.writeFileSync(idPath, sessionId + "\n", "utf-8");
      return; // write to first valid location
    } catch {
      /* ignore */
    }
  }
}

// ─── Session Registry (lightweight duplicates — statusline is standalone) ───

/** Write per-session JSON file to .claude/sessions/{sessionId}.json (heartbeat). */
function writeSessionFileStatus(projectDir, sessionId, data) {
  if (!projectDir || !sessionId) return;
  const sessionsDir = path.join(projectDir, ".claude", "sessions");
  try {
    fs.mkdirSync(sessionsDir, { recursive: true });
    const filePath = path.join(sessionsDir, `${sessionId}.json`);
    const tmpPath = filePath + ".tmp";
    const payload = {
      schema_version: 1,
      session_id: sessionId,
      active_feature: data.active_feature || null,
      plan_mode: data.plan_mode || false,
      stage_description: data.stage_description || null,
      git_branch: data.git_branch || null,
      last_heartbeat: new Date().toISOString(),
    };
    fs.writeFileSync(tmpPath, JSON.stringify(payload, null, 2) + "\n", "utf-8");
    fs.renameSync(tmpPath, filePath);
  } catch {
    /* ignore — non-critical */
  }
}

/** Count non-stale sessions active on a given feature slug. */
function countActiveSessionsStatus(projectDir, featureSlug, staleMinutes) {
  if (!projectDir || !featureSlug) return 0;
  if (staleMinutes === undefined) staleMinutes = 5;
  const sessionsDir = path.join(projectDir, ".claude", "sessions");
  if (!fs.existsSync(sessionsDir)) return 0;
  const cutoff = Date.now() - staleMinutes * 60 * 1000;
  let count = 0;
  try {
    for (const entry of fs.readdirSync(sessionsDir)) {
      if (!entry.endsWith(".json")) continue;
      try {
        const filePath = path.join(sessionsDir, entry);
        const stat = fs.statSync(filePath);
        if (stat.mtimeMs < cutoff) continue;
        const data = JSON.parse(fs.readFileSync(filePath, "utf-8"));
        if (data.active_feature === featureSlug) count++;
      } catch {
        /* ignore */
      }
    }
  } catch {
    /* ignore */
  }
  return count;
}

/** Resolve active feature: session file first, lifecycle.json root fallback. */
function resolveActiveFeatureStatus(projectDir, sessionId) {
  if (sessionId && projectDir) {
    try {
      const filePath = path.join(
        projectDir,
        ".claude",
        "sessions",
        `${sessionId}.json`,
      );
      if (fs.existsSync(filePath)) {
        const data = JSON.parse(fs.readFileSync(filePath, "utf-8"));
        if (data.active_feature) return data.active_feature;
      }
    } catch {
      /* fall through */
    }
  }
  // Fallback: lifecycle-state.json (v6+), then lifecycle.json (legacy)
  if (!projectDir) return null;
  try {
    const statePath = path.join(projectDir, ".claude", "lifecycle-state.json");
    if (fs.existsSync(statePath)) {
      const state = JSON.parse(fs.readFileSync(statePath, "utf-8"));
      if (state.active_feature) return state.active_feature;
    }
  } catch {
    /* fall through */
  }
  try {
    const lcPath = path.join(projectDir, ".claude", "lifecycle.json");
    if (!fs.existsSync(lcPath)) return null;
    const data = JSON.parse(fs.readFileSync(lcPath, "utf-8"));
    return data.active_feature || null;
  } catch {
    return null;
  }
}

/** Prune session files older than ttlHours. Called periodically from statusline. */
function pruneDeadSessionsStatus(projectDir, ttlHours) {
  if (!projectDir) return;
  if (ttlHours === undefined) ttlHours = 24;
  const sessionsDir = path.join(projectDir, ".claude", "sessions");
  if (!fs.existsSync(sessionsDir)) return;
  const cutoff = Date.now() - ttlHours * 60 * 60 * 1000;
  try {
    for (const entry of fs.readdirSync(sessionsDir)) {
      if (!entry.endsWith(".json")) continue;
      try {
        const filePath = path.join(sessionsDir, entry);
        const stat = fs.statSync(filePath);
        if (stat.mtimeMs < cutoff) fs.unlinkSync(filePath);
      } catch {
        /* ignore */
      }
    }
  } catch {
    /* ignore */
  }
}

module.exports = {
  parseStdin,
  resolveSessionId,
  readBridgeFile,
  readEckVersion,
  readRegistryProject,
  readOutputStyle,
  readProtection,
  gitBranch,
  gitDirtyCount,
  gitToplevel,
  deduplicateDirs,
  writeBridge,
  writeSessionId,
  writeSessionFileStatus,
  countActiveSessionsStatus,
  resolveActiveFeatureStatus,
  pruneDeadSessionsStatus,
};
