/**
 * Git RPC handlers — branch listing, clone, probe, and template scaffolding.
 */

import path from "path";
import fs from "fs";
import { execSync, spawn } from "child_process";

export function createGitHandlers() {
  return {
    "system.shells": async () => {
      const candidates = [
        { path: '/bin/bash', name: 'bash' },
        { path: '/bin/zsh', name: 'zsh' },
        { path: '/bin/fish', name: 'fish' },
        { path: '/usr/bin/fish', name: 'fish' },
        { path: '/bin/sh', name: 'sh' },
        { path: '/usr/bin/dash', name: 'dash' },
      ];
      const seen = new Set<string>();
      const shells: Array<{ path: string; name: string }> = [];
      for (const c of candidates) {
        if (seen.has(c.name)) continue;
        try {
          fs.statSync(c.path);
          shells.push(c);
          seen.add(c.name);
        } catch {
          // not installed
        }
      }
      const loginShell = process.env.SHELL ?? '/bin/bash';
      return { shells, loginShell };
    },

    "system.fonts": async () => {
      const PREFERRED_SANS = new Set([
        'Inter', 'Roboto', 'Noto Sans', 'Ubuntu', 'Open Sans', 'Lato',
        'Source Sans 3', 'IBM Plex Sans', 'Fira Sans', 'PT Sans',
        'Cantarell', 'DejaVu Sans', 'Liberation Sans',
      ]);
      try {
        const allRaw = execSync(
          'fc-list :style=Regular --format="%{family}\\n" | sort -u',
          { encoding: 'utf8', timeout: 5000 },
        );
        const monoRaw = execSync(
          'fc-list :spacing=mono --format="%{family}\\n" | sort -u',
          { encoding: 'utf8', timeout: 5000 },
        );

        const monoSet = new Set<string>();
        const monoFonts: Array<{ family: string; isNerdFont: boolean }> = [];
        for (const line of monoRaw.split('\n')) {
          const family = line.split(',')[0].trim(); // fc-list returns comma-separated aliases
          if (!family || monoSet.has(family)) continue;
          monoSet.add(family);
          monoFonts.push({ family, isNerdFont: family.includes('Nerd') });
        }
        monoFonts.sort((a, b) => {
          if (a.isNerdFont !== b.isNerdFont) return a.isNerdFont ? -1 : 1;
          return a.family.localeCompare(b.family);
        });

        const uiSet = new Set<string>();
        const uiFonts: Array<{ family: string; preferred: boolean }> = [];
        for (const line of allRaw.split('\n')) {
          const family = line.split(',')[0].trim();
          if (!family || uiSet.has(family) || monoSet.has(family)) continue;
          uiSet.add(family);
          uiFonts.push({ family, preferred: PREFERRED_SANS.has(family) });
        }
        uiFonts.sort((a, b) => {
          if (a.preferred !== b.preferred) return a.preferred ? -1 : 1;
          return a.family.localeCompare(b.family);
        });

        return { uiFonts, monoFonts };
      } catch (err) {
        console.error('[system.fonts] fc-list failed:', err);
        return { uiFonts: [], monoFonts: [] };
      }
    },

    "ssh.checkSshfs": async () => {
      try {
        const sshfsPath = execSync("which sshfs", { encoding: "utf8", timeout: 3000 }).trim();
        return { installed: true, path: sshfsPath };
      } catch {
        return { installed: false, path: null };
      }
    },

    "git.branches": async ({ path: repoPath }: { path: string }) => {
      try {
        const resolved = path.resolve(repoPath.replace(/^~/, process.env.HOME ?? ""));

        // Verify .git exists
        try {
          fs.statSync(path.join(resolved, ".git"));
        } catch {
          return { branches: [], current: "", error: "Not a git repository" };
        }

        const raw = execSync("git branch -a --no-color", {
          cwd: resolved,
          encoding: "utf8",
          timeout: 5000,
        });

        let current = "";
        const branches: string[] = [];

        for (const line of raw.split("\n")) {
          const trimmed = line.trim();
          if (!trimmed) continue;

          // Skip HEAD pointer lines like "remotes/origin/HEAD -> origin/main"
          if (trimmed.includes("->")) continue;

          const isCurrent = trimmed.startsWith("* ");
          const name = trimmed.replace(/^\*\s+/, "").replace(/^remotes\//, "");

          if (isCurrent) current = name;
          if (!branches.includes(name)) branches.push(name);
        }

        return { branches, current };
      } catch (err) {
        const error = err instanceof Error ? err.message : String(err);
        return { branches: [], current: "", error };
      }
    },

    "git.clone": async ({
      url,
      target,
      branch,
    }: {
      url: string;
      target: string;
      branch?: string;
    }) => {
      // Validate URL — basic check for git-cloneable patterns
      if (!url || (!url.includes("/") && !url.includes(":"))) {
        return { ok: false, error: "Invalid repository URL" };
      }

      const resolvedTarget = path.resolve(target.replace(/^~/, process.env.HOME ?? ""));

      // Don't overwrite existing directory
      try {
        fs.statSync(resolvedTarget);
        return { ok: false, error: "Target directory already exists" };
      } catch {
        // Expected — directory should not exist
      }

      return new Promise<{ ok: boolean; error?: string }>((resolve) => {
        const args = ["clone"];
        if (branch) args.push("--branch", branch);
        args.push(url, resolvedTarget);

        const proc = spawn("git", args, { stdio: "pipe", timeout: 120_000 });

        let stderr = "";
        proc.stderr?.on("data", (chunk: Buffer) => {
          stderr += chunk.toString();
        });

        proc.on("close", (code) => {
          if (code === 0) {
            resolve({ ok: true });
          } else {
            resolve({ ok: false, error: stderr.trim() || `git clone exited with code ${code}` });
          }
        });

        proc.on("error", (err) => {
          resolve({ ok: false, error: err.message });
        });
      });
    },

    "git.probe": async ({ url }: { url: string }) => {
      if (!url || (!url.includes("/") && !url.includes(":"))) {
        return { ok: false, branches: [], defaultBranch: '', error: "Invalid URL" };
      }

      return new Promise<{
        ok: boolean; branches: string[]; defaultBranch: string; error?: string;
      }>((resolve) => {
        const proc = spawn("git", ["ls-remote", "--heads", "--symref", url], {
          stdio: "pipe",
          timeout: 15_000,
        });

        let stdout = "";
        let stderr = "";
        proc.stdout?.on("data", (chunk: Buffer) => { stdout += chunk.toString(); });
        proc.stderr?.on("data", (chunk: Buffer) => { stderr += chunk.toString(); });

        proc.on("close", (code) => {
          if (code !== 0) {
            resolve({ ok: false, branches: [], defaultBranch: '', error: stderr.trim() || 'Probe failed' });
            return;
          }
          const branches: string[] = [];
          let defaultBranch = 'main';

          for (const line of stdout.split("\n")) {
            // Parse symref for HEAD
            const symMatch = line.match(/^ref: refs\/heads\/(\S+)\s+HEAD/);
            if (symMatch) { defaultBranch = symMatch[1]; continue; }

            // Parse branch refs
            const refMatch = line.match(/\trefs\/heads\/(.+)$/);
            if (refMatch && !branches.includes(refMatch[1])) {
              branches.push(refMatch[1]);
            }
          }
          resolve({ ok: true, branches, defaultBranch });
        });

        proc.on("error", (err) => {
          resolve({ ok: false, branches: [], defaultBranch: '', error: err.message });
        });
      });
    },

    "project.createFromTemplate": async ({
      templateId,
      targetDir,
      projectName,
    }: {
      templateId: string;
      targetDir: string;
      projectName: string;
    }) => {
      const resolved = path.resolve(targetDir.replace(/^~/, process.env.HOME ?? ""));
      const projectDir = path.join(resolved, projectName);

      // Don't overwrite existing directory
      try {
        fs.statSync(projectDir);
        return { ok: false, path: '', error: "Directory already exists" };
      } catch {
        // Expected
      }

      try {
        fs.mkdirSync(projectDir, { recursive: true });

        const scaffolds: Record<string, Record<string, string>> = {
          blank: {
            'README.md': `# ${projectName}\n`,
            '.gitignore': 'node_modules/\ndist/\n.env\n',
          },
          'web-app': {
            'index.html': `<!DOCTYPE html>\n<html lang="en">\n<head>\n  <meta charset="UTF-8" />\n  <meta name="viewport" content="width=device-width, initial-scale=1.0" />\n  <title>${projectName}</title>\n  <link rel="stylesheet" href="style.css" />\n</head>\n<body>\n  <h1>${projectName}</h1>\n  <script src="main.js"></script>\n</body>\n</html>\n`,
            'style.css': `body {\n  font-family: system-ui, sans-serif;\n  margin: 2rem;\n}\n`,
            'main.js': `console.log('${projectName} loaded');\n`,
            'README.md': `# ${projectName}\n\nA web application.\n`,
            '.gitignore': 'node_modules/\ndist/\n.env\n',
          },
          'api-server': {
            'index.ts': `const server = Bun.serve({\n  port: 3000,\n  fetch(req) {\n    const url = new URL(req.url);\n    if (url.pathname === '/health') {\n      return Response.json({ status: 'ok' });\n    }\n    return Response.json({ message: 'Hello from ${projectName}' });\n  },\n});\nconsole.log(\`Server running on \${server.url}\`);\n`,
            'README.md': `# ${projectName}\n\nA Bun HTTP API server.\n\n## Run\n\n\`\`\`bash\nbun run index.ts\n\`\`\`\n`,
            '.gitignore': 'node_modules/\ndist/\n.env\n',
            'package.json': `{\n  "name": "${projectName}",\n  "version": "0.1.0",\n  "scripts": {\n    "start": "bun run index.ts",\n    "dev": "bun --watch index.ts"\n  }\n}\n`,
          },
          'cli-tool': {
            'cli.ts': `#!/usr/bin/env bun\nconst args = process.argv.slice(2);\nif (args.includes('--help') || args.includes('-h')) {\n  console.log('Usage: ${projectName} [options]');\n  console.log('  --help, -h   Show this help');\n  console.log('  --version    Show version');\n  process.exit(0);\n}\nif (args.includes('--version')) {\n  console.log('${projectName} 0.1.0');\n  process.exit(0);\n}\nconsole.log('Hello from ${projectName}!');\n`,
            'README.md': `# ${projectName}\n\nA command-line tool.\n\n## Run\n\n\`\`\`bash\nbun run cli.ts --help\n\`\`\`\n`,
            '.gitignore': 'node_modules/\ndist/\n.env\n',
          },
        };

        const files = scaffolds[templateId] ?? scaffolds['blank'];
        for (const [name, content] of Object.entries(files)) {
          fs.writeFileSync(path.join(projectDir, name), content);
        }

        // Initialize git repo
        try {
          execSync('git init', { cwd: projectDir, timeout: 5000 });
        } catch {
          // Non-fatal — project created without git
        }

        return { ok: true, path: projectDir };
      } catch (err) {
        const error = err instanceof Error ? err.message : String(err);
        return { ok: false, path: '', error };
      }
    },
  };
}
