#!/usr/bin/env node

import { execFileSync } from "node:child_process";
import { existsSync, readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";

const pluginRoot = dirname(dirname(fileURLToPath(import.meta.url)));
const pluginManifestPath = join(pluginRoot, ".claude-plugin", "plugin.json");
const noColor = process.env.NO_COLOR || process.env.LAZYCLAUDE_HUD_NO_COLOR === "1";
const sep = process.env.LAZYCLAUDE_HUD_SEP || "│";

const colors = noColor
  ? { reset: "", bold: "", dim: "", accent: "", empty: "", green: "", yellow: "", red: "" }
  : {
      reset: "\x1b[0m",
      bold: "\x1b[1m",
      dim: "\x1b[38;5;245m",
      accent: "\x1b[38;5;81m",
      empty: "\x1b[38;5;238m",
      green: "\x1b[38;5;71m",
      yellow: "\x1b[38;5;178m",
      red: "\x1b[38;5;167m",
    };

const readStdin = async () => {
  let input = "";
  for await (const chunk of process.stdin) input += chunk;
  return input;
};

const readVersion = () => {
  if (process.env.LAZYCLAUDE_VERSION) return process.env.LAZYCLAUDE_VERSION;
  try {
    return JSON.parse(readFileSync(pluginManifestPath, "utf8")).version ?? "?";
  } catch {
    return "?";
  }
};

const shortModel = (raw) => {
  const compact = String(raw || "?")
    .replace(/\([^)]*\)/gu, "")
    .replace(/\[[^\]]*\]/gu, "")
    .replace(/\s+/gu, " ")
    .trim();
  const number = compact.match(/[0-9]+(?:\.[0-9]+)?/u)?.[0] ?? "";
  if (/opus/iu.test(compact)) return `O${number}`;
  if (/sonnet/iu.test(compact)) return `S${number}`;
  if (/haiku/iu.test(compact)) return `H${number}`;
  return compact.slice(0, 10) || "?";
};

const clamp = (value, min = 0, max = 100) => Math.max(min, Math.min(max, Number.isFinite(value) ? value : min));

const displayPercent = (value) => Math.round(clamp(value));

const barColor = (value) => {
  if (value >= 90) return colors.red;
  if (value >= 70) return colors.yellow;
  return colors.green;
};

const makeBlockBar = (value, width = 3) => {
  const pct = clamp(Math.round(value));
  const filled = Math.floor((pct * width) / 100);
  return `${barColor(pct)}${"█".repeat(filled)}${colors.empty}${"░".repeat(width - filled)}${colors.reset}`;
};

const makeContextBar = (value, width = 6) => {
  const pct = clamp(Math.round(value));
  const step = 100 / width;
  let output = "";
  for (let i = 0; i < width; i += 1) {
    const progress = pct - i * step;
    if (progress >= step * 0.8) output += `${colors.accent}█${colors.reset}`;
    else if (progress >= step * 0.3) output += `${colors.accent}▄${colors.reset}`;
    else output += `${colors.empty}░${colors.reset}`;
  }
  return output;
};

const latestUsageTokens = (transcriptPath) => {
  if (!transcriptPath || !existsSync(transcriptPath)) return 0;
  const lines = readFileSync(transcriptPath, "utf8").split(/\r?\n/u).filter(Boolean);
  for (let index = lines.length - 1; index >= 0; index -= 1) {
    try {
      const event = JSON.parse(lines[index]);
      const usage = event?.message?.usage;
      if (!usage || event.isSidechain === true || event.isApiErrorMessage === true) continue;
      return (
        Number(usage.input_tokens ?? 0) +
        Number(usage.cache_read_input_tokens ?? 0) +
        Number(usage.cache_creation_input_tokens ?? 0)
      );
    } catch {
    }
  }
  return 0;
};

const latestUserMessage = (transcriptPath) => {
  if (!transcriptPath || !existsSync(transcriptPath)) return "";
  const lines = readFileSync(transcriptPath, "utf8").split(/\r?\n/u).filter(Boolean);
  const ignoredPrefixes = ["[Request interrupted", "[Request cancelled", "<local-command-stdout>", "<local-command-stderr>"];
  const hasCommandXml = (text) => /<(?:command-name|command-message|command-args)\b[^>]*>/u.test(text);
  for (let index = lines.length - 1; index >= 0; index -= 1) {
    try {
      const event = JSON.parse(lines[index]);
      if (event.type !== "user") continue;
      const content = event?.message?.content;
      const text = Array.isArray(content)
        ? content.filter((part) => part?.type === "text").map((part) => part.text).join(" ")
        : content;
      const normalized = String(text || "").replace(/\s+/gu, " ").trim();
      if (normalized && !ignoredPrefixes.some((prefix) => normalized.startsWith(prefix)) && !hasCommandXml(normalized)) {
        return normalized;
      }
    } catch {
    }
  }
  return "";
};

const gitStatus = (cwd) => {
  if (!cwd || !existsSync(cwd)) return "";
  try {
    const branch = execFileSync("git", ["-C", cwd, "branch", "--show-current"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
    if (!branch) return "";
    const status = execFileSync("git", ["-C", cwd, "--no-optional-locks", "status", "--porcelain", "-uall"], {
      encoding: "utf8",
      stdio: ["ignore", "pipe", "ignore"],
    }).trim();
    const count = status ? status.split(/\r?\n/u).length : 0;
    let sync = "✓";
    try {
      execFileSync("git", ["-C", cwd, "rev-parse", "--abbrev-ref", "@{upstream}"], { stdio: "ignore" });
      const [ahead, behind] = execFileSync("git", ["-C", cwd, "rev-list", "--left-right", "--count", "HEAD...@{upstream}"], {
        encoding: "utf8",
        stdio: ["ignore", "pipe", "ignore"],
      }).trim().split(/\s+/u).map(Number);
      sync = `${ahead > 0 ? `↑${ahead}` : ""}${behind > 0 ? `↓${behind}` : ""}` || "✓";
    } catch {
      sync = "✓";
    }
    return `${branch}${count > 0 ? ` +${count}` : ""} ${sync}`;
  } catch {
    return "";
  }
};

const firstNumber = (...values) => {
  for (const value of values) {
    const number = Number(value);
    if (Number.isFinite(number)) return number;
  }
  return null;
};

const firstString = (...values) => {
  for (const value of values) {
    if (typeof value === "string" && value.trim()) return value.trim();
  }
  return "";
};

const parseUsage = (status) => {
  const raw = process.env.LAZYCLAUDE_HUD_TEST_USAGE || process.env.CLAUDE_HUD_TEST_USAGE || "";
  const values = {};
  for (const part of raw.split(",")) {
    const [key, value] = part.split("=");
    if (!key || value === undefined) continue;
    values[key.trim()] = value.trim();
  }
  const fiveHour = status.rate_limits?.five_hour ?? status.rate_limits?.["5_hour"] ?? {};
  const sevenDay = status.rate_limits?.seven_day ?? status.rate_limits?.["7_day"] ?? status.rate_limits?.weekly ?? {};
  const fiveHourPct = firstNumber(values["5h"], values.five_hour, fiveHour.used_percentage, fiveHour.percentage, fiveHour.usedPercent);
  const weeklyPct = firstNumber(values["1w"], values.wk, values.seven_day, sevenDay.used_percentage, sevenDay.percentage, sevenDay.usedPercent);
  return {
    fiveHour: fiveHourPct === null ? null : clamp(fiveHourPct),
    fiveHourReset: firstString(values.reset5, values.reset5h, fiveHour.resets_at, fiveHour.reset_at, fiveHour.reset_time),
    weekly: weeklyPct === null ? null : clamp(weeklyPct),
    weeklyReset: firstString(values.reset1, values.resetw, values.reset7, values.reset7d, sevenDay.resets_at, sevenDay.reset_at, sevenDay.reset_time),
  };
};

const formatReset = (iso) => {
  if (!iso) return "";
  const target = Date.parse(iso);
  if (!Number.isFinite(target)) return "";
  const minutes = Math.max(0, Math.floor((target - Date.now()) / 60000));
  const hours = Math.floor(minutes / 60);
  const days = Math.floor(hours / 24);
  if (days > 0) return `(${days}d${hours % 24}h)`;
  if (hours > 0) return `(${hours}h${minutes % 60}m)`;
  return `(${minutes}m)`;
};

const formatUsageSegment = (label, value, reset) => {
  if (value === null) return `${label} [${colors.empty}░░░${colors.reset}]${colors.dim}--%${colors.reset}`;
  const pct = displayPercent(value);
  return `${label} [${makeBlockBar(pct)}]${barColor(pct)}${pct}%${colors.dim}${formatReset(reset)}${colors.reset}`;
};

const main = async () => {
  const input = await readStdin();
  const status = input.trim() ? JSON.parse(input) : {};
  const version = readVersion();
  const model = shortModel(status.model?.display_name ?? status.model?.id);
  const maxContext = Number(status.context_window?.context_window_size ?? 200000);
  const maxK = Math.max(1, Math.round(maxContext / 1000));
  const tokens = latestUsageTokens(status.transcript_path);
  const estimatedTokens = tokens > 0 ? tokens : 20000;
  const statusContextPct = firstNumber(status.context_window?.used_percentage, status.context_window?.usage_percentage);
  const rawContextPct = statusContextPct === null ? Math.floor((estimatedTokens * 100) / maxContext) : statusContextPct;
  const contextPct = displayPercent(rawContextPct);
  const usage = parseUsage(status);
  const usageText = ` ${sep} ${formatUsageSegment("5h", usage.fiveHour, usage.fiveHourReset)} ${sep} ${formatUsageSegment("1w", usage.weekly, usage.weeklyReset)}`;
  const git = gitStatus(status.cwd);
  const prefix = `${colors.bold}${colors.accent}[💤LAZYCLAUDE v${version}]${colors.reset}`;
  const line = `${prefix} | ${colors.accent}${model}${colors.dim} ${sep} ctx ${makeContextBar(contextPct)} ${contextPct}%/${maxK}k${colors.reset}${usageText}${git ? ` ${sep} git ${git}` : ""}`;
  process.stdout.write(`${line}\n`);

  const lastMessage = latestUserMessage(status.transcript_path);
  if (lastMessage) {
    const trimmed = lastMessage.length > 120 ? `${lastMessage.slice(0, 117)}...` : lastMessage;
    process.stdout.write(`└─ ${trimmed}\n`);
  }
};

main().catch((error) => {
  process.stdout.write(`[💤LAZYCLAUDE v${readVersion()}] | HUD unavailable: ${error.message}\n`);
  process.exit(0);
});
