"use strict";

const fs = require("fs");
const path = require("path");
const { shortenPath } = require("./helpers");
const {
  parseStdin,
  resolveSessionId,
  readBridgeFile,
  readEckVersion,
  readRegistryProject,
  readOutputStyle,
  readProtection,
  gitBranch,
  gitDirtyCount,
  deduplicateDirs,
  writeBridge,
  writeSessionId,
  writeSessionFileStatus,
  countActiveSessionsStatus,
  resolveActiveFeatureStatus,
  pruneDeadSessionsStatus,
} = require("./data-gathering");
const {
  readLifecycleData,
  readLifecycleSteps,
  readLifecycleFeature,
  readLifecycleFeatureId,
  readParkedCount,
  readBacklogCount,
  readBestIncompleteFeature,
} = require("./lifecycle");
const {
  DEFAULT_LAYOUT,
  loadConfig,
  collectConfigRows,
  collectDirRows,
} = require("./layout-engine");
const { renderTable } = require("./grid-renderer");

// ─── Main ──────────────────────────────────────────────────────────────────

function main() {
  // Parse stdin (single JSON.parse replaces 19 jq calls)
  const stdin = parseStdin();

  // Terminal width — Claude Code >=2.1.139 runs statusLine hooks without TTY
  // access, so stty/tput can't reach the real terminal (tput cols returns a
  // stub 80 with no TTY). The stdin schema has no terminal-width field, so we
  // rely on COLUMNS env (if the user exports it) and otherwise default wide.
  let termWidth = parseInt(process.env.COLUMNS, 10) || 0;
  if (!termWidth) {
    try {
      termWidth = process.stdout.columns || process.stderr.columns || 0;
    } catch {
      /* ignore */
    }
  }
  if (!termWidth) termWidth = 160;
  if (termWidth > 220) termWidth = 220;

  // Session + bridge
  const sessionId = resolveSessionId();
  const bridge = readBridgeFile(sessionId);

  // Extract model — handle both string and object forms
  let model;
  if (stdin.model) {
    if (typeof stdin.model === "object") {
      model = stdin.model.display_name || stdin.model.id || "unknown";
    } else {
      model = String(stdin.model);
    }
  } else {
    model = "unknown";
  }

  // Context data
  const contextPercent =
    stdin.context_window && stdin.context_window.used_percentage != null
      ? Math.round(stdin.context_window.used_percentage)
      : null;
  const contextWindowSize =
    stdin.context_window && stdin.context_window.context_window_size
      ? stdin.context_window.context_window_size
      : null;

  // Duration
  const durationMs =
    (stdin.cost && stdin.cost.total_duration_ms) || stdin.duration_ms || 0;

  // Cost
  const costUsd =
    (stdin.cost && stdin.cost.total_cost_usd) || stdin.cost_usd || 0;

  // I/O tokens
  const totalInputTokens =
    stdin.context_window && stdin.context_window.total_input_tokens
      ? stdin.context_window.total_input_tokens
      : 0;
  const totalOutputTokens =
    stdin.context_window && stdin.context_window.total_output_tokens
      ? stdin.context_window.total_output_tokens
      : 0;

  // Cache tokens
  const currentUsage =
    stdin.context_window && stdin.context_window.current_usage;
  const cacheCreate = currentUsage
    ? currentUsage.cache_creation_input_tokens
    : 0;
  const cacheRead = currentUsage ? currentUsage.cache_read_input_tokens : 0;
  const inputTokens = currentUsage ? currentUsage.input_tokens : 0;

  // Lines changed
  const linesAdded = (stdin.cost && stdin.cost.total_lines_added) || 0;
  const linesRemoved = (stdin.cost && stdin.cost.total_lines_removed) || 0;

  // Directories
  const stdinCwd =
    (stdin.workspace && stdin.workspace.current_dir) ||
    stdin.cwd ||
    process.cwd();
  const stdinProjectDir =
    stdin.workspace && stdin.workspace.project_dir
      ? stdin.workspace.project_dir
      : null;
  const stdinAddedDirs =
    stdin.workspace && Array.isArray(stdin.workspace.added_dirs)
      ? stdin.workspace.added_dirs
      : [];

  // The live status channel skills write through is `stageDescription` (via
  // update-stage.sh → bridge file). compStatus reads it directly now, so we
  // no longer need a separate `statusText` stub.
  const stageDesc = (bridge && bridge.stageDescription) || "Ready";

  // Rate limits (Claude.ai Pro/Max only) — optional, may be undefined.
  const rateLimits = stdin.rate_limits || null;

  // Data gathering
  const bridgeBranch = bridge && bridge.gitBranch ? bridge.gitBranch : null;
  const projectBranch = stdinProjectDir ? gitBranch(stdinProjectDir) : null;
  const currentBranch = bridgeBranch || gitBranch(stdinCwd);
  const projectDirty = stdinProjectDir ? gitDirtyCount(stdinProjectDir) : null;
  const currentDirty = gitDirtyCount(stdinCwd);
  const dedupedAdded = deduplicateDirs(
    stdinAddedDirs,
    stdinProjectDir,
    stdinCwd,
  );

  const pathMax = Math.min(Math.max(Math.floor(termWidth / 3), 30), 60);

  // Output style from stdin
  const stdinOutputStyle =
    stdin.output_style && stdin.output_style.name
      ? stdin.output_style.name
      : null;

  // Read lifecycle state — single parse for all lifecycle queries (#625)
  const resolvedActiveFeature = resolveActiveFeatureStatus(
    stdinProjectDir || stdinCwd,
    sessionId,
  );
  const lcData = readLifecycleData(stdinProjectDir || stdinCwd);
  const lifecycleSteps = readLifecycleSteps(
    stdinProjectDir || stdinCwd,
    lcData,
  );
  const lifecycleFeature = readLifecycleFeature(
    stdinProjectDir || stdinCwd,
    lcData,
  );
  const lifecycleFeatureId = readLifecycleFeatureId(
    stdinProjectDir || stdinCwd,
    lcData,
  );

  // Detect project setup state for next-cmd component
  const projectBaseDir = stdinProjectDir || stdinCwd;
  const isEckProject = (() => {
    if (!projectBaseDir) return false;
    try {
      return fs.existsSync(
        path.join(projectBaseDir, ".claude", "project-constants.md"),
      );
    } catch {
      return false;
    }
  })();
  // Multi-signal project configuration detection:
  // 1. lifecycle.json exists → features tracked, project is configured
  // 2. .start-project-completed sentinel → backward compat
  // 3. PROJECT_KEY filled in project-constants.md → template was populated
  const isProjectConfigured = (() => {
    if (!projectBaseDir) return false;
    const claudeDir = path.join(projectBaseDir, ".claude");
    try {
      // Signal 1: lifecycle-registry.json or lifecycle.json exists (most common for active projects)
      if (fs.existsSync(path.join(claudeDir, "lifecycle-registry.json")))
        return true;
      if (fs.existsSync(path.join(claudeDir, "lifecycle.json"))) return true;
      // Signal 2: sentinel file (backward compat with /start-project)
      if (fs.existsSync(path.join(claudeDir, ".start-project-completed")))
        return true;
      // Signal 3: PROJECT_KEY is filled in (not a {PLACEHOLDER})
      const pcPath = path.join(claudeDir, "project-constants.md");
      if (fs.existsSync(pcPath)) {
        const pc = fs.readFileSync(pcPath, "utf-8");
        const m = pc.match(/\|\s*PROJECT_KEY\s*\|\s*([^|\n]+)/);
        if (m && m[1].trim() && !m[1].includes("{")) return true;
      }
    } catch {
      /* ignore */
    }
    return false;
  })();

  // Session conflict detection
  const sessionConflict = resolvedActiveFeature
    ? countActiveSessionsStatus(projectBaseDir, resolvedActiveFeature)
    : 0;

  // Build data bag
  const dataBag = {
    lifecycleSteps,
    lifecycleFeature,
    lifecycleFeatureId,
    sessionConflict,
    isEckProject,
    isProjectConfigured,
    parkedFeatureCount: readParkedCount(projectBaseDir, lcData),
    backlogCount: readBacklogCount(projectBaseDir, lcData),
    bestIncompleteFeature: readBestIncompleteFeature(projectBaseDir, lcData),
    model,
    eckVersion: readEckVersion(),
    stageDescription: stageDesc,
    rateLimits,
    contextPercent,
    contextWindowSize,
    durationMs,
    costUsd,
    totalInputTokens,
    totalOutputTokens,
    cacheCreate,
    cacheRead,
    inputTokens,
    linesAdded,
    linesRemoved,
    ...readRegistryProject(stdinProjectDir || stdinCwd),
    outputStyle: readOutputStyle(stdinOutputStyle),
    protectionState: readProtection(),
    termWidth,
    // Dir row data
    projectDir: stdinProjectDir,
    projectDirShort: stdinProjectDir
      ? shortenPath(stdinProjectDir, pathMax)
      : null,
    projectBranch,
    projectDirty,
    currentDir: stdinCwd,
    currentDirShort: shortenPath(stdinCwd, pathMax),
    currentBranch,
    currentDirty,
    addedDirs: dedupedAdded,
    stdinProjectDir,
    stdinCwd,
    pathMax,
    // Pass git helpers for added dirs in collectDirRows
    _gitBranch: gitBranch,
    _gitDirtyCount: gitDirtyCount,
  };

  // Layout: collect all row component arrays, render as unified table
  const config = loadConfig();
  const layout = config || DEFAULT_LAYOUT;
  const configRowComps = collectConfigRows(layout, dataBag);
  const dirRowComps = collectDirRows(dataBag);
  const allRowComps = [...configRowComps, ...dirRowComps];
  const allRows = renderTable(allRowComps, termWidth);

  // Output — R1 is brand_version on its own row
  process.stdout.write(allRows.join("\n") + "\n");

  // Write bridge file
  writeBridge(sessionId, contextPercent, stageDesc, currentBranch);

  // Persist session ID so update-stage.sh can find the bridge file
  writeSessionId(sessionId, stdinProjectDir || stdinCwd);

  // Write per-session file (heartbeat) and prune stale sessions
  const sessionProjectDir = stdinProjectDir || stdinCwd;
  const planModeActive = (() => {
    if (!sessionProjectDir) return false;
    try {
      return fs.existsSync(path.join(sessionProjectDir, ".planning-session"));
    } catch {
      return false;
    }
  })();
  writeSessionFileStatus(sessionProjectDir, sessionId, {
    active_feature: resolvedActiveFeature,
    plan_mode: planModeActive,
    stage_description: stageDesc,
    git_branch: currentBranch,
  });
  pruneDeadSessionsStatus(sessionProjectDir);
}

module.exports = { main };
