import fs from "node:fs";
import fsp from "node:fs/promises";
import path from "node:path";
import type {
  ExtensionAPI,
  Theme as PiTheme,
} from "@mariozechner/pi-coding-agent";
import { Theme } from "@mariozechner/pi-coding-agent";

const THEME_NAME = "matugen";
const THEME_PATH = path.join(
  process.env.HOME ?? "",
  ".pi/agent/themes/matugen.json",
);
const POLL_INTERVAL_MS = 500;

type ColorValue = string | number;

type ThemeJson = {
  name: string;
  vars?: Record<string, ColorValue>;
  colors: Record<string, ColorValue>;
};

const BG_KEYS = new Set([
  "selectedBg",
  "userMessageBg",
  "customMessageBg",
  "toolPendingBg",
  "toolSuccessBg",
  "toolErrorBg",
]);

function detectColorMode(): "truecolor" | "256color" {
  const colorterm = process.env.COLORTERM;
  if (colorterm === "truecolor" || colorterm === "24bit") return "truecolor";
  if (process.env.WT_SESSION) return "truecolor";
  const term = process.env.TERM || "";
  if (term === "dumb" || term === "" || term === "linux") return "256color";
  if (process.env.TERM_PROGRAM === "Apple_Terminal") return "256color";
  return "truecolor";
}

function resolveVarRefs(
  value: ColorValue,
  vars: Record<string, ColorValue>,
  visited = new Set<string>(),
): ColorValue {
  if (
    typeof value === "number" ||
    value === "" ||
    (typeof value === "string" && value.startsWith("#"))
  ) {
    return value;
  }
  if (typeof value !== "string") {
    throw new Error(`Invalid color value: ${String(value)}`);
  }
  if (visited.has(value)) {
    throw new Error(`Circular variable reference: ${value}`);
  }
  if (!(value in vars)) {
    throw new Error(`Variable reference not found: ${value}`);
  }
  visited.add(value);
  return resolveVarRefs(vars[value], vars, visited);
}

function resolveThemeColors(
  colors: Record<string, ColorValue>,
  vars: Record<string, ColorValue> = {},
): Record<string, ColorValue> {
  const resolved: Record<string, ColorValue> = {};
  for (const [key, value] of Object.entries(colors)) {
    resolved[key] = resolveVarRefs(value, vars);
  }
  return resolved;
}

async function loadThemeFromPath(filePath: string): Promise<PiTheme> {
  const content = await fsp.readFile(filePath, "utf-8");
  const parsed = JSON.parse(content) as ThemeJson;
  const resolved = resolveThemeColors(parsed.colors, parsed.vars ?? {});
  const fgColors: Record<string, ColorValue> = {};
  const bgColors: Record<string, ColorValue> = {};
  for (const [key, value] of Object.entries(resolved)) {
    if (BG_KEYS.has(key)) {
      bgColors[key] = value;
    } else {
      fgColors[key] = value;
    }
  }
  return new Theme(fgColors as never, bgColors as never, detectColorMode(), {
    name: parsed.name ?? THEME_NAME,
    sourcePath: filePath,
  });
}

export default function matugenThemeWatch(pi: ExtensionAPI) {
  let watcher: fs.StatWatcher | null = null;
  let debounce: NodeJS.Timeout | null = null;

  pi.on("session_start", (_event, ctx) => {
    try {
      watcher = fs.watchFile(
        THEME_PATH,
        { interval: POLL_INTERVAL_MS },
        (curr, prev) => {
          if (curr.mtimeMs === prev.mtimeMs) return;

          if (debounce) {
            clearTimeout(debounce);
          }

          debounce = setTimeout(async () => {
            // i wish this fucking worked
            // pi.sendUserMessage("/reload", { deliverAs: "followUp" });
            const theme = await loadThemeFromPath(THEME_PATH);
            const res = ctx.ui.setTheme(theme);
            if (res.success) {
              ctx.ui.notify("Background changed. Colors: ", "info");
            } else {
              ctx.ui.notify("Theme update failed, fuck", "error");
            }
          }, 150);
        },
      );
    } catch (error) {
      ctx.ui.notify(`Theme watch failed: ${String(error)}`, "error");
    }
  });

  pi.on("session_shutdown", () => {
    if (watcher) {
      // eslint-disable-next-line @typescript-eslint/no-explicit-any -- weird types
      fs.unwatchFile(THEME_PATH, watcher as any);
      watcher = null;
    }
    if (debounce) {
      clearTimeout(debounce);
      debounce = null;
    }
  });
}
