/**
 * Status line renderer — 严格复刻 stormzhang/token-tracker 的 hook script。
 *
 * 参考: https://github.com/stormzhang/token-tracker/blob/master/src/hooks.py (HOOK_SCRIPT)
 *
 * 数据源：stdin JSON（Claude Code 每次渲染 statusLine 时传入）
 * 输出：3 行 `\n` 分隔，每行内部用 ` | ` 分隔 segment；宽度不够时分级降级。
 */
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { execSync } from 'child_process';

const STATUS_FILE = path.join(os.homedir(), '.cache', 'ffctk', 'tt-status.json');

const C = {
  green: '\x1b[32m',
  yellow: '\x1b[33m',
  red: '\x1b[31m',
  cyan: '\x1b[36m',
  blue: '\x1b[34m',
  magenta: '\x1b[35m',
  peach: '\x1b[38;5;216m',
  dim: '\x1b[2m',
  reset: '\x1b[0m',
};

const ANSI_RE = /\x1b\[[0-9;]*m/g;
function vlen(s: string): number {
  return s.replace(ANSI_RE, '').length;
}

/**
 * 按可见宽度截断字符串，保留 ANSI color code。超长则末尾追加省略号。
 * 不在窄终端里把多行折叠（Claude Code 渲染 statusLine 时一旦某行视宽超出
 * 终端宽度就会换行，看起来像三行变一行）—— 这里强制每行不超过 W，让三行
 * 始终各自占一行。
 */
function truncateAnsi(s: string, maxW: number): string {
  if (vlen(s) <= maxW) return s;
  const limit = Math.max(0, maxW - 1);
  let out = '';
  let visible = 0;
  let i = 0;
  while (i < s.length && visible < limit) {
    if (s[i] === '\x1b' && s[i + 1] === '[') {
      const end = s.indexOf('m', i);
      if (end >= 0) {
        out += s.slice(i, end + 1);
        i = end + 1;
        continue;
      }
    }
    out += s[i];
    visible++;
    i++;
  }
  return out + C.reset + C.dim + '…' + C.reset;
}

function colorByPct(pct: number): string {
  return pct < 50 ? C.green : pct < 80 ? C.yellow : C.red;
}

function fmtTokens(n: number): string {
  if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
  if (n >= 1_000) return `${Math.round(n / 1_000)}k`;
  return String(n);
}

function fmtDuration(seconds: number): string {
  if (seconds >= 86400) {
    const d = Math.floor(seconds / 86400);
    const rem = Math.floor(seconds % 86400);
    return `${d}d${Math.floor(rem / 3600)}h`;
  }
  if (seconds >= 3600) {
    const h = Math.floor(seconds / 3600);
    const m = Math.floor((seconds % 3600) / 60);
    return `${h}h${m}m`;
  }
  if (seconds >= 60) return `${Math.floor(seconds / 60)}min`;
  return `${Math.floor(seconds)}s`;
}

function progressBar(pct: number | null, barW = 8): string {
  // 改用 ▰▱（U+25B0 / U+25B1）—— 两者笔画粗细一致，
  // 不像 █░ 那样空白部分视觉偏淡而填充部分偏重
  if (pct == null) return '▱'.repeat(barW) + ' n/a';
  const v = Math.max(0, Math.min(100, pct));
  const filled = Math.round((v / 100) * barW);
  return `${colorByPct(v)}${'▰'.repeat(filled)}${C.reset}${C.dim}${'▱'.repeat(barW - filled)}${C.reset} ${v.toFixed(0)}%`;
}

function getWidth(): number {
  // Token-tracker 用 /dev/tty 读真实终端宽度。Status line 在 Claude Code 内被调用，
  // process.stdout.columns 通常拿不到。fallback 116（与 token-tracker 同）。
  try {
    const c = process.stdout.columns;
    if (c && c > 1) return Math.max(1, c - 4);
  } catch {
    /* ignore */
  }
  if (process.platform !== 'win32') {
    try {
      const r = execSync('stty size < /dev/tty 2>/dev/null', { stdio: ['ignore', 'pipe', 'ignore'] })
        .toString()
        .trim();
      const cols = parseInt(r.split(' ')[1] ?? '', 10);
      if (cols > 1) return Math.max(1, cols - 4);
    } catch {
      /* ignore */
    }
  }
  return 116;
}

/**
 * 用户工作流：从主仓库 spawn Claude Code，然后 tool call 里 `cd` 到 slot/worktree 做事。
 * 这时 Claude Code 报告的 workspace.project_dir 仍是主仓库，但 *实际* 工作目录在 worktree。
 *
 * 解决：扫 transcript jsonl 最近的 Bash tool call，解析 `cd /abs/path` 拿真实 cwd。
 * 找不到则回退到 project_dir。
 */
/**
 * 累计 transcript 所有 assistant turn 的 token 用量（session 级真实总数）。
 * Claude Code 在 status line stdin 里给的 `context_window.total_*_tokens` 实际是
 * "当前 context 内可见的 token"，不是 session 累计——长 session 后差距很大。
 *
 * 同时返回最近 cd 目标作为真实工作目录（一次扫文件做两件事，省 IO）。
 */
interface TranscriptStats {
  cwd: string;
  cumulativeInput: number;
  cumulativeOutput: number;
  cumulativeCacheRead: number;
}

function scanTranscript(transcriptPath: string | undefined, fallbackCwd: string): TranscriptStats {
  const result: TranscriptStats = {
    cwd: fallbackCwd,
    cumulativeInput: 0,
    cumulativeOutput: 0,
    cumulativeCacheRead: 0,
  };
  if (!transcriptPath || !fs.existsSync(transcriptPath)) return result;
  try {
    const content = fs.readFileSync(transcriptPath, 'utf8');
    const lines = content.split('\n');
    let latestCwd = '';
    const tail = lines.slice(Math.max(0, lines.length - 200));
    // assistant turn token 累计走全文（小 session 也只几 MB），cd 检测只看最近 200 行
    for (const line of lines) {
      if (!line.trim()) continue;
      try {
        const o = JSON.parse(line);
        if (o.type === 'assistant') {
          const u = o.message?.usage;
          if (u) {
            // cumulativeInput 必须把 cache_creation_input_tokens 算进去，
            // 才能跟"本轮 in"（turnInTotal = input + cache_creation）口径一致；
            // 否则长 session 会出现「累计 in < 本轮 in」的反逻辑。
            result.cumulativeInput +=
              Number(u.input_tokens ?? 0) + Number(u.cache_creation_input_tokens ?? 0);
            result.cumulativeOutput += Number(u.output_tokens ?? 0);
            result.cumulativeCacheRead += Number(u.cache_read_input_tokens ?? 0);
          }
        }
      } catch {
        /* skip */
      }
    }
    for (const line of tail) {
      if (!line.trim()) continue;
      try {
        const o = JSON.parse(line);
        const content = o.message?.content;
        if (!Array.isArray(content)) continue;
        for (const c of content) {
          if (c?.type !== 'tool_use') continue;
          const cmd: string | undefined = c?.input?.command;
          if (!cmd) continue;
          const m = cmd.match(/(?:^|\s|&&|;|\|)\s*cd\s+(?:"|')?([^\s"'&|;]+)/);
          if (m && path.isAbsolute(m[1])) latestCwd = m[1];
        }
      } catch {
        /* skip */
      }
    }
    if (latestCwd) result.cwd = latestCwd;
  } catch {
    /* skip */
  }
  return result;
}

function detectActualCwd(transcriptPath: string | undefined, fallback: string): string {
  if (!transcriptPath || !fs.existsSync(transcriptPath)) return fallback;
  try {
    const content = fs.readFileSync(transcriptPath, 'utf8');
    const lines = content.split('\n');
    // 只看最近 100 行，避免大 transcript 性能
    const tail = lines.slice(Math.max(0, lines.length - 200));
    let latestCwd = '';
    for (const line of tail) {
      if (!line.trim()) continue;
      try {
        const o = JSON.parse(line);
        // assistant tool_use: Bash command
        const content = o.message?.content;
        if (!Array.isArray(content)) continue;
        for (const c of content) {
          if (c?.type !== 'tool_use') continue;
          // Bash tool 或类似命令工具
          const cmd: string | undefined = c?.input?.command;
          if (!cmd) continue;
          // 匹配 cd /abs/path（带或不带后续 && /;）
          const m = cmd.match(/(?:^|\s|&&|;|\|)\s*cd\s+(?:"|')?([^\s"'&|;]+)/);
          if (m && path.isAbsolute(m[1])) {
            latestCwd = m[1];
          }
        }
      } catch {
        /* skip */
      }
    }
    return latestCwd || fallback;
  } catch {
    return fallback;
  }
}

/**
 * 从 cwd 路径推断 worktree 标签：
 *   /home/.../.agent-pool/slot-3/...     →  "slot-3"
 *   /home/.../ffworkspace-wt/asset-mgmt/ →  "asset-mgmt"
 *   /home/.../Code/workspace/...          →  ""   （主仓库不标）
 */
function worktreeLabel(cwd: string): string {
  const m1 = cwd.match(/\.agent-pool\/(slot-\d+)/);
  if (m1) return m1[1];
  const m2 = cwd.match(/\/ffworkspace-wt\/([^/]+)/);
  if (m2 && m2[1] !== '.agent-pool') return m2[1];
  return '';
}

/**
 * 主仓库名：从 git common-dir 反推，所有 worktree 共享同一主仓库。
 * `git rev-parse --git-common-dir` → 主仓库 .git 路径；.. 即主仓库根，basename 即 project name。
 * 主仓库本身 fallback 到 cwd basename。
 */
function repoName(cwd: string): string {
  try {
    const commonDir = execSync('git rev-parse --git-common-dir', {
      cwd,
      timeout: 2000,
      stdio: ['ignore', 'pipe', 'ignore'],
    })
      .toString()
      .trim();
    if (!commonDir) return path.basename(cwd);
    const abs = path.isAbsolute(commonDir) ? commonDir : path.resolve(cwd, commonDir);
    return path.basename(path.dirname(abs));
  } catch {
    return path.basename(cwd);
  }
}

function gitBranch(cwd: string): string {
  try {
    const branch = execSync('git branch --show-current', { cwd, timeout: 2000, stdio: ['ignore', 'pipe', 'ignore'] })
      .toString()
      .trim();
    if (!branch) return '';
    try {
      const dirty = execSync('git status --porcelain --untracked-files=no', {
        cwd,
        timeout: 2000,
        stdio: ['ignore', 'pipe', 'ignore'],
      })
        .toString()
        .trim();
      return dirty ? branch + '*' : branch;
    } catch {
      return branch;
    }
  } catch {
    return '';
  }
}

function readStdin(): string {
  try {
    return fs.readFileSync(0, 'utf8');
  } catch {
    return '';
  }
}

function saveStatus(data: any) {
  try {
    fs.mkdirSync(path.dirname(STATUS_FILE), { recursive: true });
    const tmp = STATUS_FILE + '.tmp';
    fs.writeFileSync(tmp, JSON.stringify({ ...data, _received_at: new Date().toISOString() }), { mode: 0o600 });
    fs.renameSync(tmp, STATUS_FILE);
  } catch {
    /* ignore */
  }
}

export function renderStatusline(): string {
  const raw = readStdin();
  let data: any = {};
  if (raw.trim()) {
    try {
      data = JSON.parse(raw);
      saveStatus(data);
    } catch {
      /* ignore */
    }
  }

  const W = getWidth();
  const barW = W >= 100 ? 8 : W >= 60 ? 6 : 4;
  const nowTs = Math.floor(Date.now() / 1000);
  const ctx = data.context_window ?? {};

  // 一次扫 transcript 拿真实 cwd + 累计 token 用量
  const tx = scanTranscript(data.transcript_path, data.workspace?.project_dir ?? '');

  // ===== Line 1: Project[wt](branch) | 时长 | model =====
  const line1: string[] = [];
  const projectDir = data.workspace?.project_dir ?? '';
  if (projectDir) {
    const actualCwd = tx.cwd;
    const name = repoName(actualCwd);
    const wt = worktreeLabel(actualCwd);
    const branch = gitBranch(actualCwd);
    const wtPart = wt ? `${C.dim}[${C.reset}${C.cyan}${wt}${C.reset}${C.dim}]${C.reset}` : '';
    const branchPart = branch ? `(${C.magenta}${branch}${C.reset})` : '';
    line1.push(`${C.green}${name}${C.reset}${wtPart}${branchPart}`);
  }
  const durMs = data.cost?.total_duration_ms;
  if (durMs && durMs > 0) {
    line1.push(`${C.dim}${C.magenta}时长: ${fmtDuration(durMs / 1000)}${C.reset}`);
  }
  // Model：去掉 "(1M context)" 后缀；effort 大写 + 三空格分隔；不带 /fast/nofast
  let modelName: string = data.model?.display_name ?? '';
  if (modelName) {
    modelName = modelName.replace(/\s*\([^)]*context\)\s*$/, '').trim();
    const effort = data.effort?.level ?? '';
    if (effort) {
      const cap = effort.charAt(0).toUpperCase() + effort.slice(1);
      modelName = `${modelName} (${cap})`;
    }
    line1.push(`${C.dim}${C.magenta}${modelName}${C.reset}`);
  }

  // ===== Line 2: 5h | 7d | Context（按行宽度降级：full → no-reset → pct-only）=====
  const rl = data.rate_limits ?? {};
  // 准备三种渲染粒度：full / noReset / pctOnly
  type Part = { full: string; noReset: string; pctOnly: string };
  const parts: Part[] = [];
  for (const [key, label] of [['five_hour', '5h'], ['seven_day', '7d']] as const) {
    const entry = rl[key];
    const pct = entry?.used_percentage;
    if (pct == null) continue;
    let resetStr = '';
    const ra = entry?.resets_at;
    if (ra) {
      const remain = Math.floor(ra) - nowTs;
      if (remain > 0) resetStr = ` ${C.dim}(${fmtDuration(remain)})${C.reset}`;
    }
    parts.push({
      full: `${C.blue}${label}:${C.reset}${progressBar(pct, barW)}${resetStr}`,
      noReset: `${C.blue}${label}:${C.reset}${progressBar(pct, barW)}`,
      pctOnly: `${C.blue}${label}:${C.reset}${pct.toFixed(0)}%`,
    });
  }
  if (ctx.used_percentage != null) {
    parts.push({
      full: `${C.blue}Context:${C.reset}${progressBar(ctx.used_percentage, barW)}`,
      noReset: `${C.blue}Context:${C.reset}${progressBar(ctx.used_percentage, barW)}`,
      pctOnly: `${C.blue}Context:${C.reset}${ctx.used_percentage.toFixed(0)}%`,
    });
  }
  const pickLevel = (level: 'full' | 'noReset' | 'pctOnly') => parts.map((p) => p[level]);
  let line2 = pickLevel('full');
  if (vlen(line2.join(' | ')) > W) {
    line2 = pickLevel('noReset');
    if (vlen(line2.join(' | ')) > W) {
      line2 = pickLevel('pctOnly');
    }
  }

  // ===== Line 3: Tokens + Cached + Cost =====
  const line3: string[] = [];
  // Session 累计走 transcript 求和（statusline stdin 的 total_* 实际是 current ctx 内值）
  const totalIn = tx.cumulativeInput || (ctx.total_input_tokens ?? 0);
  const totalOut = tx.cumulativeOutput || (ctx.total_output_tokens ?? 0);
  const cumulativeCacheRead = tx.cumulativeCacheRead;
  const cur = ctx.current_usage ?? {};
  const turnInTotal = (cur.input_tokens ?? 0) + (cur.cache_creation_input_tokens ?? 0);
  const turnOut = cur.output_tokens ?? 0;
  const turnStr = ` ${C.dim}(本轮: in ${fmtTokens(turnInTotal)}, out ${fmtTokens(turnOut)})${C.reset}`;
  if (totalIn || totalOut) {
    line3.push(`${C.peach}Tokens: in ${fmtTokens(totalIn)}, out ${fmtTokens(totalOut)}${turnStr}`);
  }
  // Cached 同样取累计；fallback 到 current_usage
  const cacheReadDisplay = cumulativeCacheRead || (cur.cache_read_input_tokens ?? 0);
  if (cacheReadDisplay > 0) {
    line3.push(`${C.cyan}Cached: ${fmtTokens(cacheReadDisplay)}${C.reset}`);
  }
  const usd = data.cost?.total_cost_usd;
  if (usd != null) {
    line3.push(`${C.magenta}Cost: $${usd.toFixed(2)}${C.reset}`);
  }

  const output = [line1, line2, line3]
    .filter((l) => l.length > 0)
    .map((l) => truncateAnsi(l.join(' | '), W));
  return output.length ? output.join('\n') + '\n' : '';
}
