import { join, resolve, sep } from "node:path";
import { dirname } from "node:path";
import { homedir } from "node:os";

function assertUnderRoot(resolved: string, root: string): string {
  const normRoot = resolve(root);
  const norm = resolve(resolved);
  if (norm !== normRoot && !norm.startsWith(normRoot + sep)) {
    throw new Error(`path traversal rejected: ${resolved}`);
  }
  return norm;
}

function projectFewtokPath(projectRoot: string, ...segments: string[]): string {
  const root = resolve(projectRoot);
  return assertUnderRoot(join(root, ".fewtok", ...segments), root);
}

function home(): string {
  return homedir();
}

function root(): string {
  const override = process.env.FEWTOK_HOME;
  return override ? resolve(override) : join(home(), ".fewtok");
}

export const paths = {
  home,
  root,
  state: () => join(root(), "state"),
  cache: () => join(root(), "cache"),
  cacheReads: () => join(root(), "cache", "reads"),
  cacheResults: () => join(root(), "cache", "results"),
  logs: () => join(root(), "logs"),
  global: () => join(root(), "global"),
  globalPaths: () => join(root(), "global-path.json"),
  globalMacros: () => join(root(), "global-macro.json"),
  config: () => join(root(), "config.json"),
  statsDb: () => join(root(), "stats.db"),
  registryDb: () => join(root(), "registry.db"),
  pidFile: () => join(root(), "state", "proxy.pid"),
  portFile: () => join(root(), "state", "proxy.port"),
  readyFile: (pid: number) => join(root(), "state", "ready-" + String(pid)),
  bypassDir: () => join(root(), "state", "bypass"),
  bypassFile: (sessionId: string) => join(root(), "state", "bypass", sessionId),
  sessionPidsDir: () => join(root(), "state", "session-pids"),
  sessionPidFile: (pid: number) => join(root(), "state", "session-pids", String(pid)),
  projectDir: (r: string) => projectFewtokPath(r),
  projectPaths: (r: string) => projectFewtokPath(r, "paths.json"),
  projectMacros: (r: string) => projectFewtokPath(r, "macros.json"),
  subdirPaths: (r: string) => projectFewtokPath(r, "subdir-paths.json"),
  subdirMacros: (r: string) => projectFewtokPath(r, "subdir-macros.json"),
  projectConfig: (r: string) => projectFewtokPath(r, "config.json"),
  lastScanMarker: (r: string) =>
    join(root(), "state", "last_scan_" + Buffer.from(r).toString("hex") + ".ts"),
  installerSnapshot: (homeDir: string, label: string) => {
    const safeLabel = label.replace(/[^a-zA-Z0-9_-]/g, "").slice(0, 64);
    if (!safeLabel) throw new Error("invalid snapshot label");
    const snapshotsRoot = join(resolve(homeDir), ".fewtok", "state", "install-snapshots");
    const ts = new Date().toISOString().replace(/:/g, "-");
    return assertUnderRoot(join(snapshotsRoot, safeLabel, `${ts}.json`), snapshotsRoot);
  },
  // Points to the dist/ directory regardless of whether we're running from
  // source (src/lifecycle/) or bundled into dist/. Bun preserves import.meta.dir
  // as the source file's directory at bundle time, so climbing ../../ from
  // src/lifecycle/ always resolves to the package root's dist/.
  distDir: () => join(dirname(dirname(import.meta.dir)), "dist"),
};
