/**
 * Miscellaneous RPC handlers — memora, keybindings, updater, diagnostics, telemetry,
 * files.pickDirectory, files.homeDir, project.templates, project.clone.
 *
 * These are small handlers that don't warrant their own file.
 */

import fs from "fs";
import { Database } from "bun:sqlite";
import { homedir } from "os";
import { join } from "path";
import { randomUUID } from "crypto";
import type { SettingsDb } from "../settings-db.ts";
import type { PtyClient } from "../pty-client.ts";
import type { RelayClient } from "../relay-client.ts";
import type { SidecarManager } from "../sidecar-manager.ts";
import { checkForUpdates, getLastCheckTimestamp } from "../updater.ts";
import type { TelemetryManager } from "../telemetry.ts";

// ── Helpers ─────────────────────────────────────────────────────────────────

const BRANCH_RE = /^[a-zA-Z0-9/_.-]+$/;

async function gitWorktreeAdd(
  mainRepoPath: string,
  worktreePath: string,
  branchName: string,
): Promise<{ ok: boolean; error?: string }> {
  const proc = Bun.spawn(
    ["git", "worktree", "add", worktreePath, "-b", branchName],
    { cwd: mainRepoPath, stderr: "pipe", stdout: "pipe" },
  );
  const exitCode = await proc.exited;
  if (exitCode !== 0) {
    const errText = await new Response(proc.stderr).text();
    return { ok: false, error: errText.trim() || `git exited with code ${exitCode}` };
  }
  return { ok: true };
}

// ── Types ───────────────────────────────────────────────────────────────────

interface MiscDeps {
  settingsDb: SettingsDb;
  ptyClient: PtyClient;
  relayClient: RelayClient;
  sidecarManager: SidecarManager;
  telemetry: TelemetryManager;
  appVersion: string;
}

// ── Handler factory ─────────────────────────────────────────────────────────

export function createMiscHandlers(deps: MiscDeps) {
  const { settingsDb, ptyClient, relayClient, sidecarManager, telemetry, appVersion } = deps;

  return {
    // ── Files: picker + homeDir ─────────────────────────────────────────
    "files.pickDirectory": async ({ startingFolder, createIfMissing }: { startingFolder?: string; createIfMissing?: boolean }) => {
      try {
        const { execSync } = await import("child_process");
        const fs = await import("fs");
        const home = process.env.HOME || "/home";
        let start = (startingFolder || "~/").replace(/^~/, home);
        // Resolve to nearest existing parent
        while (start && start !== "/" && !fs.existsSync(start)) {
          start = start.replace(/\/[^/]+\/?$/, "") || home;
        }
        if (!start || !fs.existsSync(start)) start = home;
        const result = execSync(
          `zenity --file-selection --directory --title="Select Project Folder" --filename="${start}/"`,
          { encoding: "utf-8", timeout: 120_000 },
        ).trim();
        if (result && createIfMissing) {
          fs.mkdirSync(result, { recursive: true });
        }
        return { path: result || null };
      } catch (err: unknown) {
        // Exit code 1 = user cancelled — not an error
        if (err && typeof err === "object" && "status" in err && (err as {status:number}).status === 1) {
          return { path: null };
        }
        return { path: null };
      }
    },

    "files.homeDir": async () => ({ path: process.env.HOME || "/home" }),

    // ── Project templates ───────────────────────────────────────────────
    "project.templates": async () => ({
      templates: [
        { id: "blank", name: "Blank Project", description: "Empty directory with no scaffolding", icon: "📁" },
        { id: "web-app", name: "Web App", description: "HTML/CSS/JS web application starter", icon: "🌐" },
        { id: "api-server", name: "API Server", description: "Node.js/Bun HTTP API server", icon: "⚡" },
        { id: "cli-tool", name: "CLI Tool", description: "Command-line tool with argument parser", icon: "🔧" },
      ],
    }),

    // ── Project clone ───────────────────────────────────────────────────
    "project.clone": async ({ projectId, branchName }: { projectId: string; branchName: string }) => {
      try {
        if (!BRANCH_RE.test(branchName)) {
          return { ok: false, error: "Invalid branch name. Use only letters, numbers, /, _, -, ." };
        }
        const source = settingsDb.getProject(projectId);
        if (!source) return { ok: false, error: `Project not found: ${projectId}` };

        const mainRepoPath = source.mainRepoPath ?? source.cwd;
        const allProjects = settingsDb.listProjects();
        const existingClones = allProjects.filter(
          (p) => p.cloneOf === projectId || (source.cloneOf && p.cloneOf === source.cloneOf),
        );
        if (existingClones.length >= 3) return { ok: false, error: "Maximum 3 clones per project reached" };

        const cloneIndex = existingClones.length + 1;
        const wtSuffix = randomUUID().slice(0, 8);
        const worktreePath = `${mainRepoPath}-wt-${wtSuffix}`;

        const gitResult = await gitWorktreeAdd(mainRepoPath, worktreePath, branchName);
        if (!gitResult.ok) return { ok: false, error: gitResult.error };

        const cloneId = `${projectId}-clone-${cloneIndex}-${randomUUID().slice(0, 8)}`;
        const cloneConfig = {
          id: cloneId, name: `${source.name} [${branchName}]`,
          cwd: worktreePath, accent: source.accent, provider: source.provider,
          profile: source.profile, model: source.model,
          groupId: source.groupId ?? "dev", mainRepoPath,
          cloneOf: projectId, worktreePath, worktreeBranch: branchName, cloneIndex,
        };
        settingsDb.setProject(cloneId, cloneConfig);
        return { ok: true, project: { id: cloneId, config: JSON.stringify(cloneConfig) } };
      } catch (err) {
        const error = err instanceof Error ? err.message : String(err);
        console.error("[project.clone]", err);
        return { ok: false, error };
      }
    },

    // ── Keybindings ─────────────────────────────────────────────────────
    "keybindings.getAll": () => {
      try { return { keybindings: settingsDb.getKeybindings() }; }
      catch (err) { console.error("[keybindings.getAll]", err); return { keybindings: {} }; }
    },
    "keybindings.set": ({ id, chord }: { id: string; chord: string }) => {
      try { settingsDb.setKeybinding(id, chord); return { ok: true }; }
      catch (err) { console.error("[keybindings.set]", err); return { ok: false }; }
    },
    "keybindings.reset": ({ id }: { id: string }) => {
      try { settingsDb.deleteKeybinding(id); return { ok: true }; }
      catch (err) { console.error("[keybindings.reset]", err); return { ok: false }; }
    },

    // ── Updater ─────────────────────────────────────────────────────────
    "updater.check": async () => {
      try {
        const result = await checkForUpdates(appVersion);
        return { ...result, error: undefined };
      } catch (err) {
        const error = err instanceof Error ? err.message : String(err);
        console.error("[updater.check]", err);
        return { available: false, version: "", downloadUrl: "", releaseNotes: "", checkedAt: Date.now(), error };
      }
    },
    "updater.getVersion": () => ({ version: appVersion, lastCheck: getLastCheckTimestamp() }),

    // ── Memora (read-only) ──────────────────────────────────────────────
    "memora.search": ({ query, limit }: { query: string; limit?: number }) => {
      try {
        const dbPath = join(homedir(), ".local", "share", "memora", "memories.db");
        if (!fs.existsSync(dbPath)) return { memories: [] };
        const db = new Database(dbPath, { readonly: true });
        try {
          const rows = db.query(
            "SELECT id, content, tags, metadata, created_at as createdAt, updated_at as updatedAt FROM memories WHERE content LIKE ? ORDER BY updated_at DESC LIMIT ?",
          ).all(`%${query}%`, limit ?? 20);
          return { memories: rows as Array<{ id: number; content: string; tags: string; metadata: string; createdAt: string; updatedAt: string }> };
        } finally { db.close(); }
      } catch (err) { console.error("[memora.search]", err); return { memories: [] }; }
    },
    "memora.list": ({ limit, tag }: { limit?: number; tag?: string }) => {
      try {
        const dbPath = join(homedir(), ".local", "share", "memora", "memories.db");
        if (!fs.existsSync(dbPath)) return { memories: [] };
        const db = new Database(dbPath, { readonly: true });
        try {
          let sql = "SELECT id, content, tags, metadata, created_at as createdAt, updated_at as updatedAt FROM memories";
          const params: unknown[] = [];
          if (tag) { sql += " WHERE tags LIKE ?"; params.push(`%${tag}%`); }
          sql += " ORDER BY updated_at DESC LIMIT ?";
          params.push(limit ?? 20);
          const rows = db.query(sql).all(...params);
          return { memories: rows as Array<{ id: number; content: string; tags: string; metadata: string; createdAt: string; updatedAt: string }> };
        } finally { db.close(); }
      } catch (err) { console.error("[memora.list]", err); return { memories: [] }; }
    },

    // ── Diagnostics ─────────────────────────────────────────────────────
    "diagnostics.stats": () => ({
      ptyConnected: ptyClient.isConnected,
      relayConnections: relayClient.listMachines().filter((m) => m.status === "connected").length,
      activeSidecars: sidecarManager.listSessions().filter((s) => s.status === "running").length,
      rpcCallCount: 0,
      droppedEvents: 0,
    }),

    // ── Telemetry ───────────────────────────────────────────────────────
    "telemetry.log": ({ level, message, attributes }: { level: string; message: string; attributes?: Record<string, string | number | boolean> }) => {
      try { telemetry.log(level as "info" | "warn" | "error", `[frontend] ${message}`, attributes ?? {}); return { ok: true }; }
      catch (err) { console.error("[telemetry.log]", err); return { ok: false }; }
    },
  };
}
