/**
 * File I/O RPC handlers — list, read, write with path traversal protection.
 */

import path from "path";
import fs from "fs";
import { guardPath } from "./path-guard.ts";

export function createFilesHandlers() {
  return {
    "files.list": async ({ path: dirPath }: { path: string }) => {
      const guard = guardPath(dirPath);
      if (!guard.valid) {
        console.error(`[files.list] blocked: ${guard.error}`);
        return { entries: [], error: guard.error };
      }
      try {
        const dirents = fs.readdirSync(guard.resolved, { withFileTypes: true });
        const entries = dirents
          .filter((d) => !d.name.startsWith("."))
          .map((d) => {
            let size = 0;
            if (d.isFile()) {
              try {
                size = fs.statSync(path.join(guard.resolved, d.name)).size;
              } catch { /* ignore stat errors */ }
            }
            return {
              name: d.name,
              type: (d.isDirectory() ? "dir" : "file") as "file" | "dir",
              size,
            };
          })
          .sort((a, b) => {
            if (a.type !== b.type) return a.type === "dir" ? -1 : 1;
            return a.name.localeCompare(b.name);
          });
        return { entries };
      } catch (err) {
        const error = err instanceof Error ? err.message : String(err);
        console.error("[files.list]", error);
        return { entries: [], error };
      }
    },

    // Unguarded directory listing for the PathBrowser (wizard).
    // Only returns dir names — no file content access.
    "files.browse": async ({ path: dirPath }: { path: string }) => {
      try {
        const resolved = path.resolve(dirPath.replace(/^~/, process.env.HOME || "/home"));
        const dirents = fs.readdirSync(resolved, { withFileTypes: true });
        const entries = dirents
          .filter((d) => !d.name.startsWith(".") && d.isDirectory())
          .map((d) => ({ name: d.name, type: "dir" as const, size: 0 }))
          .sort((a, b) => a.name.localeCompare(b.name));
        return { entries };
      } catch (err) {
        return { entries: [], error: err instanceof Error ? err.message : String(err) };
      }
    },

    "files.read": async ({ path: filePath }: { path: string }) => {
      const guard = guardPath(filePath);
      if (!guard.valid) {
        console.error(`[files.read] blocked: ${guard.error}`);
        return { encoding: "utf8" as const, size: 0, error: guard.error };
      }
      try {
        const stat = fs.statSync(guard.resolved);
        const MAX_SIZE = 10 * 1024 * 1024;
        if (stat.size > MAX_SIZE) {
          return { encoding: "utf8" as const, size: stat.size, error: `File too large (${(stat.size / 1024 / 1024).toFixed(1)}MB). Maximum is 10MB.` };
        }

        const buf = Buffer.alloc(Math.min(8192, stat.size));
        const fd = fs.openSync(guard.resolved, "r");
        fs.readSync(fd, buf, 0, buf.length, 0);
        fs.closeSync(fd);

        const isBinary = buf.includes(0);
        if (isBinary) {
          const content = fs.readFileSync(guard.resolved).toString("base64");
          return { content, encoding: "base64" as const, size: stat.size };
        }

        const content = fs.readFileSync(guard.resolved, "utf8");
        return { content, encoding: "utf8" as const, size: stat.size };
      } catch (err) {
        const error = err instanceof Error ? err.message : String(err);
        console.error("[files.read]", error);
        return { encoding: "utf8" as const, size: 0, error };
      }
    },

    // Feature 2: Get file stat (mtime) for conflict detection
    "files.stat": async ({ path: filePath }: { path: string }) => {
      const guard = guardPath(filePath);
      if (!guard.valid) {
        return { mtimeMs: 0, size: 0, error: guard.error };
      }
      try {
        const stat = fs.statSync(guard.resolved);
        return { mtimeMs: stat.mtimeMs, size: stat.size };
      } catch (err) {
        const error = err instanceof Error ? err.message : String(err);
        return { mtimeMs: 0, size: 0, error };
      }
    },

    // Extended stat for ProjectWizard — directory check, git detection, writability.
    // Note: This handler bypasses guardPath intentionally — the wizard needs to validate
    // arbitrary paths the user types (not just project CWDs). It only reads metadata, never content.
    "files.statEx": async ({ path: filePath }: { path: string }) => {
      try {
        const resolved = path.resolve(filePath.replace(/^~/, process.env.HOME ?? ""));
        let stat: fs.Stats;
        try {
          stat = fs.statSync(resolved);
        } catch {
          return { exists: false, isDirectory: false, isGitRepo: false };
        }

        const isDirectory = stat.isDirectory();
        let isGitRepo = false;
        let gitBranch: string | undefined;
        let writable = false;

        if (isDirectory) {
          // Check for .git directory
          try {
            const gitStat = fs.statSync(path.join(resolved, ".git"));
            isGitRepo = gitStat.isDirectory();
          } catch { /* not a git repo */ }

          // Read current branch if git repo
          if (isGitRepo) {
            try {
              const head = fs.readFileSync(path.join(resolved, ".git", "HEAD"), "utf8").trim();
              if (head.startsWith("ref: refs/heads/")) {
                gitBranch = head.slice("ref: refs/heads/".length);
              }
            } catch { /* ignore */ }
          }

          // Check writability
          try {
            fs.accessSync(resolved, fs.constants.W_OK);
            writable = true;
          } catch { /* not writable */ }
        }

        return {
          exists: true,
          isDirectory,
          isGitRepo,
          gitBranch,
          size: stat.size,
          writable,
        };
      } catch (err) {
        const error = err instanceof Error ? err.message : String(err);
        return { exists: false, isDirectory: false, isGitRepo: false, error };
      }
    },

    "files.ensureDir": async ({ path: dirPath }: { path: string }) => {
      try {
        const resolved = path.resolve(dirPath.replace(/^~/, process.env.HOME ?? ""));
        fs.mkdirSync(resolved, { recursive: true });
        return { ok: true, path: resolved };
      } catch (err) {
        return { ok: false, error: err instanceof Error ? err.message : String(err) };
      }
    },

    "files.write": async ({ path: filePath, content }: { path: string; content: string }) => {
      const guard = guardPath(filePath);
      if (!guard.valid) {
        console.error(`[files.write] blocked: ${guard.error}`);
        return { ok: false, error: guard.error };
      }
      try {
        // Feature 2: Atomic write via temp file + rename
        const tmpPath = guard.resolved + ".agor-tmp";
        fs.writeFileSync(tmpPath, content, "utf8");
        fs.renameSync(tmpPath, guard.resolved);
        return { ok: true };
      } catch (err) {
        const error = err instanceof Error ? err.message : String(err);
        console.error("[files.write]", error);
        return { ok: false, error };
      }
    },
  };
}
