import * as vscode from "vscode";
import * as path from "node:path";
import * as fs from "node:fs";

import { SqliteReader } from "./backend/sqlite";
import type { GraphNode } from "./backend/sqlite";
import { CliWrapper } from "./backend/cli";
import {
  CodeGraphTreeProvider,
  BlastRadiusTreeProvider,
  StatsTreeProvider,
} from "./views/treeView";
import { GraphWebviewPanel } from "./views/graphWebview";
import { Installer } from "./onboarding/installer";
import { registerWalkthroughCommands, showWelcomeIfNeeded } from "./onboarding/welcome";
import { StatusBar } from "./views/statusBar";
import { ScmDecorationProvider } from "./features/scmDecorations";

let sqliteReader: SqliteReader | undefined;
let autoUpdateTimer: ReturnType<typeof setTimeout> | undefined;
let scmDecorationProvider: ScmDecorationProvider | undefined;

/**
 * Locate the graph database file in the workspace.
 * Checks `.code-review-graph/graph.db` first, then falls back to `.code-review-graph.db`.
 */
function findGraphDb(workspaceRoot: string): string | undefined {
  const primary = path.join(workspaceRoot, ".code-review-graph", "graph.db");
  if (fs.existsSync(primary)) {
    return primary;
  }

  const fallback = path.join(workspaceRoot, ".code-review-graph.db");
  if (fs.existsSync(fallback)) {
    return fallback;
  }

  return undefined;
}

/**
 * Get the workspace root folder path, or undefined if no workspace is open.
 * Checks all workspace folders for a graph database (multi-root support).
 */
function getWorkspaceRoot(): string | undefined {
  const folders = vscode.workspace.workspaceFolders;
  if (!folders) { return undefined; }

  // Prefer the folder that has a graph database
  for (const folder of folders) {
    if (findGraphDb(folder.uri.fsPath)) {
      return folder.uri.fsPath;
    }
  }

  // Fall back to first folder
  return folders[0]?.uri.fsPath;
}


/**
 * Navigate to a node's source file location.
 */
async function navigateToNode(node: GraphNode): Promise<void> {
  const workspaceRoot = getWorkspaceRoot();
  const filePath = workspaceRoot
    ? path.join(workspaceRoot, node.filePath)
    : node.filePath;

  const doc = await vscode.workspace.openTextDocument(filePath);
  const line = Math.max(0, (node.lineStart ?? 1) - 1);
  await vscode.window.showTextDocument(doc, {
    selection: new vscode.Range(line, 0, line, 0),
  });
}

/**
 * Register all extension commands.
 */
function registerCommands(
  context: vscode.ExtensionContext,
  cli: CliWrapper
): void {
  context.subscriptions.push(
    vscode.commands.registerCommand(
      "codeReviewGraph.buildGraph",
      async () => {
        const workspaceRoot = getWorkspaceRoot();
        if (!workspaceRoot) {
          vscode.window.showErrorMessage("No workspace folder is open.");
          return;
        }

        const result = await cli.buildGraph(workspaceRoot);
        if (result.success) {
          await reinitialize(context);
          vscode.window.showInformationMessage("Code Graph: Build complete.");
        } else {
          vscode.window.showErrorMessage(
            `Code Graph: Build failed. ${result.stderr}`
          );
        }
      }
    )
  );

  context.subscriptions.push(
    vscode.commands.registerCommand(
      "codeReviewGraph.updateGraph",
      async () => {
        const workspaceRoot = getWorkspaceRoot();
        if (!workspaceRoot) {
          vscode.window.showErrorMessage("No workspace folder is open.");
          return;
        }

        await vscode.window.withProgress(
          {
            location: vscode.ProgressLocation.Notification,
            title: "Code Graph: Updating graph...",
            cancellable: false,
          },
          async () => {
            const result = await cli.updateGraph(workspaceRoot);
            if (!result.success) {
              vscode.window.showErrorMessage(
                `Code Graph: Update failed. ${result.stderr}`
              );
            }
          }
        );
      }
    )
  );

  context.subscriptions.push(
    vscode.commands.registerCommand(
      "codeReviewGraph.showBlastRadius",
      async (qualifiedNameOrUri?: string | vscode.Uri) => {
        if (!sqliteReader) {
          vscode.window.showWarningMessage(
            "Code Graph: No graph database loaded."
          );
          return;
        }

        let qualifiedName: string | undefined;
        if (typeof qualifiedNameOrUri === "string") {
          qualifiedName = qualifiedNameOrUri;
        } else {
          qualifiedName = await vscode.window.showInputBox({
            prompt:
              "Enter the qualified name (e.g., my_module.MyClass.my_method)",
            placeHolder: "my_module.my_function",
          });
        }

        if (!qualifiedName) {
          return;
        }

        // Find the file for this node and compute impact radius
        const node = sqliteReader.getNode(qualifiedName);
        if (!node) {
          vscode.window.showInformationMessage(
            `Code Graph: Node "${qualifiedName}" not found.`
          );
          return;
        }

        const config = vscode.workspace.getConfiguration("codeReviewGraph");
        const depth = config.get<number>("blastRadiusDepth", 2);
        const impact = sqliteReader.getImpactRadius([node.filePath], depth);

        if (impact.impactedNodes.length === 0) {
          vscode.window.showInformationMessage(
            `Code Graph: No blast radius found for "${qualifiedName}".`
          );
          return;
        }

        await vscode.commands.executeCommand(
          "codeReviewGraph.blastRadius.focus"
        );
      }
    )
  );

  context.subscriptions.push(
    vscode.commands.registerCommand(
      "codeReviewGraph.findCallers",
      async (qualifiedName?: string) => {
        if (!sqliteReader) {
          vscode.window.showWarningMessage(
            "Code Graph: No graph database loaded."
          );
          return;
        }

        if (!qualifiedName) {
          qualifiedName = await vscode.window.showInputBox({
            prompt: "Enter the qualified name to find callers for",
            placeHolder: "my_module.my_function",
          });
        }

        if (!qualifiedName) {
          return;
        }

        const edges = sqliteReader.getEdgesByTarget(qualifiedName);
        const callerEdges = edges.filter((e) => e.kind === "CALLS");

        if (callerEdges.length === 0) {
          vscode.window.showInformationMessage(
            `Code Graph: No callers found for "${qualifiedName}".`
          );
          return;
        }

        const items = callerEdges.map((e) => {
          const callerNode = sqliteReader!.getNode(e.sourceQualified);
          return {
            label: callerNode?.name ?? e.sourceQualified,
            description: callerNode?.filePath ?? e.filePath,
            detail: `Line ${callerNode?.lineStart ?? e.line}`,
            node: callerNode,
            edge: e,
          };
        });

        const selected = await vscode.window.showQuickPick(items, {
          placeHolder: `Callers of ${qualifiedName}`,
        });

        if (selected?.node) {
          await navigateToNode(selected.node);
        }
      }
    )
  );

  context.subscriptions.push(
    vscode.commands.registerCommand(
      "codeReviewGraph.findTests",
      async (qualifiedName?: string) => {
        if (!sqliteReader) {
          vscode.window.showWarningMessage(
            "Code Graph: No graph database loaded."
          );
          return;
        }

        if (!qualifiedName) {
          qualifiedName = await vscode.window.showInputBox({
            prompt: "Enter the qualified name to find tests for",
            placeHolder: "my_module.my_function",
          });
        }

        if (!qualifiedName) {
          return;
        }

        // Find tests via TESTED_BY edges
        const edges = sqliteReader.getEdgesByTarget(qualifiedName);
        const testEdges = edges.filter((e) => e.kind === "TESTED_BY");

        // Also check reverse: source is the node, target is the test
        const outEdges = sqliteReader.getEdgesBySource(qualifiedName);
        const outTestEdges = outEdges.filter((e) => e.kind === "TESTED_BY");

        const allTestQualifiedNames = new Set([
          ...testEdges.map((e) => e.sourceQualified),
          ...outTestEdges.map((e) => e.targetQualified),
        ]);

        if (allTestQualifiedNames.size === 0) {
          vscode.window.showInformationMessage(
            `Code Graph: No tests found for "${qualifiedName}".`
          );
          return;
        }

        const items: Array<{
          label: string;
          description: string;
          detail: string;
          node: GraphNode | undefined;
        }> = [];

        for (const tqn of allTestQualifiedNames) {
          const testNode = sqliteReader.getNode(tqn);
          items.push({
            label: testNode?.name ?? tqn,
            description: testNode?.filePath ?? "",
            detail: `Line ${testNode?.lineStart ?? "?"}`,
            node: testNode,
          });
        }

        const selected = await vscode.window.showQuickPick(items, {
          placeHolder: `Tests for ${qualifiedName}`,
        });

        if (selected?.node) {
          await navigateToNode(selected.node);
        }
      }
    )
  );

  // -----------------------------------------------------------------
  // codeReviewGraph.queryGraph — expose all 8 query patterns
  // -----------------------------------------------------------------
  context.subscriptions.push(
    vscode.commands.registerCommand("codeReviewGraph.queryGraph", async () => {
      if (!sqliteReader) {
        vscode.window.showWarningMessage("Code Graph: No graph database loaded.");
        return;
      }

      const patterns = [
        { label: "callers_of", description: "Find functions calling the target" },
        { label: "callees_of", description: "Find functions called by the target" },
        { label: "imports_of", description: "Find modules imported by a file" },
        { label: "importers_of", description: "Find files importing from the target" },
        { label: "children_of", description: "Find nodes contained in a file or class" },
        { label: "tests_for", description: "Find tests for a function or class" },
        { label: "inheritors_of", description: "Find classes inheriting/implementing the target" },
        { label: "file_summary", description: "List all nodes in a file" },
      ];

      const pattern = await vscode.window.showQuickPick(patterns, {
        placeHolder: "Select a query pattern",
      });
      if (!pattern) { return; }

      const target = await vscode.window.showInputBox({
        prompt: `Enter the target for ${pattern.label}`,
        placeHolder: "e.g., my_module.py::my_function or path/to/file.py",
      });
      if (!target) { return; }

      // Map pattern to edge kind + direction
      type QueryDef = { edgeKind: string; direction: "incoming" | "outgoing"; nodeFilter?: string };
      const queryMap: Record<string, QueryDef> = {
        callers_of: { edgeKind: "CALLS", direction: "incoming" },
        callees_of: { edgeKind: "CALLS", direction: "outgoing" },
        imports_of: { edgeKind: "IMPORTS_FROM", direction: "outgoing" },
        importers_of: { edgeKind: "IMPORTS_FROM", direction: "incoming" },
        children_of: { edgeKind: "CONTAINS", direction: "outgoing" },
        tests_for: { edgeKind: "TESTED_BY", direction: "incoming" },
        inheritors_of: { edgeKind: "INHERITS", direction: "incoming" },
        file_summary: { edgeKind: "CONTAINS", direction: "outgoing" },
      };

      const qdef = queryMap[pattern.label];
      if (!qdef) { return; }

      // Try exact match, then search
      let node = sqliteReader.getNode(target);
      if (!node) {
        const matches = sqliteReader.searchNodes(target, 5);
        if (matches.length === 1) {
          node = matches[0];
        } else if (matches.length > 1) {
          const selected = await vscode.window.showQuickPick(
            matches.map(m => ({
              label: m.name,
              description: `${m.kind} · ${m.filePath}`,
              node: m,
            })),
            { placeHolder: `Multiple matches for "${target}" — select one` },
          );
          if (!selected) { return; }
          node = selected.node;
        }
      }

      if (!node) {
        vscode.window.showInformationMessage(`Code Graph: "${target}" not found.`);
        return;
      }

      const edges = qdef.direction === "incoming"
        ? sqliteReader.getEdgesByTarget(node.qualifiedName)
        : sqliteReader.getEdgesBySource(node.qualifiedName);

      const filtered = edges.filter(e => e.kind === qdef.edgeKind);

      if (filtered.length === 0) {
        vscode.window.showInformationMessage(
          `Code Graph: No ${pattern.label} results for "${node.name}".`
        );
        return;
      }

      const items = filtered.map(e => {
        const relatedQn = qdef.direction === "incoming" ? e.sourceQualified : e.targetQualified;
        const relatedNode = sqliteReader!.getNode(relatedQn);
        return {
          label: relatedNode?.name ?? relatedQn,
          description: relatedNode ? `${relatedNode.kind} · ${relatedNode.filePath}` : "",
          detail: `Line ${relatedNode?.lineStart ?? e.line}`,
          node: relatedNode,
        };
      });

      const selected = await vscode.window.showQuickPick(items, {
        placeHolder: `${pattern.label}: ${node.name} (${filtered.length} results)`,
      });

      if (selected?.node) {
        await navigateToNode(selected.node);
      }
    })
  );

  // -----------------------------------------------------------------
  // codeReviewGraph.findCallees
  // -----------------------------------------------------------------
  context.subscriptions.push(
    vscode.commands.registerCommand(
      "codeReviewGraph.findCallees",
      async (qualifiedName?: string) => {
        if (!sqliteReader) {
          vscode.window.showWarningMessage("Code Graph: No graph database loaded.");
          return;
        }

        if (!qualifiedName) {
          qualifiedName = await vscode.window.showInputBox({
            prompt: "Enter the qualified name to find callees for",
            placeHolder: "my_module.my_function",
          });
        }
        if (!qualifiedName) { return; }

        const edges = sqliteReader.getEdgesBySource(qualifiedName);
        const calleeEdges = edges.filter(e => e.kind === "CALLS");

        if (calleeEdges.length === 0) {
          vscode.window.showInformationMessage(
            `Code Graph: No callees found for "${qualifiedName}".`
          );
          return;
        }

        const items = calleeEdges.map(e => {
          const calleeNode = sqliteReader!.getNode(e.targetQualified);
          return {
            label: calleeNode?.name ?? e.targetQualified,
            description: calleeNode?.filePath ?? e.filePath,
            detail: `Line ${calleeNode?.lineStart ?? e.line}`,
            node: calleeNode,
          };
        });

        const selected = await vscode.window.showQuickPick(items, {
          placeHolder: `Callees of ${qualifiedName}`,
        });

        if (selected?.node) {
          await navigateToNode(selected.node);
        }
      }
    )
  );

  // -----------------------------------------------------------------
  // codeReviewGraph.findLargeFunctions
  // -----------------------------------------------------------------
  context.subscriptions.push(
    vscode.commands.registerCommand(
      "codeReviewGraph.findLargeFunctions",
      async () => {
        if (!sqliteReader) {
          vscode.window.showWarningMessage("Code Graph: No graph database loaded.");
          return;
        }

        const minLinesStr = await vscode.window.showInputBox({
          prompt: "Minimum line count threshold",
          placeHolder: "50",
          value: "50",
        });
        if (!minLinesStr) { return; }

        const minLines = parseInt(minLinesStr, 10);
        if (isNaN(minLines) || minLines < 1) {
          vscode.window.showWarningMessage("Code Graph: Invalid line count.");
          return;
        }

        const results = sqliteReader.getNodesBySize(minLines, undefined, undefined, 50);

        if (results.length === 0) {
          vscode.window.showInformationMessage(
            `Code Graph: No functions found with ${minLines}+ lines.`
          );
          return;
        }

        const items = results.map(r => ({
          label: `${r.name} (${r.lineCount} lines)`,
          description: `${r.kind} · ${r.filePath}`,
          detail: `Lines ${r.lineStart ?? "?"}–${r.lineEnd ?? "?"}`,
          node: r as GraphNode,
        }));

        const selected = await vscode.window.showQuickPick(items, {
          placeHolder: `${results.length} nodes with ${minLines}+ lines`,
        });

        if (selected?.node) {
          await navigateToNode(selected.node);
        }
      }
    )
  );

  // -----------------------------------------------------------------
  // codeReviewGraph.embedGraph
  // -----------------------------------------------------------------
  context.subscriptions.push(
    vscode.commands.registerCommand("codeReviewGraph.embedGraph", async () => {
      const workspaceRoot = getWorkspaceRoot();
      if (!workspaceRoot) {
        vscode.window.showErrorMessage("No workspace folder is open.");
        return;
      }

      const result = await cli.embedGraph(workspaceRoot);
      if (result.success) {
        vscode.window.showInformationMessage("Code Graph: Embeddings computed.");
      } else {
        const msg = result.stderr.includes("not installed")
          ? "Install embeddings support: pip install code-review-graph[embeddings]"
          : `Embedding failed: ${result.stderr}`;
        vscode.window.showErrorMessage(`Code Graph: ${msg}`);
      }
    })
  );

  // -----------------------------------------------------------------
  // codeReviewGraph.watchGraph
  // -----------------------------------------------------------------
  context.subscriptions.push(
    vscode.commands.registerCommand("codeReviewGraph.watchGraph", async () => {
      const workspaceRoot = getWorkspaceRoot();
      if (!workspaceRoot) {
        vscode.window.showErrorMessage("No workspace folder is open.");
        return;
      }

      vscode.window.showInformationMessage("Code Graph: Watch mode started.");
      const result = await cli.watchGraph(workspaceRoot);
      if (!result.success) {
        vscode.window.showErrorMessage(
          `Code Graph: Watch failed. ${result.stderr}`
        );
      }
    })
  );

  context.subscriptions.push(
    vscode.commands.registerCommand("codeReviewGraph.showGraph", async () => {
      if (!sqliteReader) {
        vscode.window.showWarningMessage(
          "Code Graph: No graph database loaded. Run 'Code Graph: Build Graph' first."
        );
        return;
      }

      GraphWebviewPanel.createOrShow(context.extensionUri, sqliteReader);
    })
  );

  context.subscriptions.push(
    vscode.commands.registerCommand("codeReviewGraph.search", async () => {
      if (!sqliteReader) {
        vscode.window.showWarningMessage(
          "Code Graph: No graph database loaded."
        );
        return;
      }

      const query = await vscode.window.showInputBox({
        prompt: "Search the code graph",
        placeHolder: "Enter a function, class, or module name",
      });

      if (!query) {
        return;
      }

      const results = sqliteReader.searchNodes(query);

      if (results.length === 0) {
        vscode.window.showInformationMessage(
          `Code Graph: No results found for "${query}".`
        );
        return;
      }

      const items = results.map((r) => ({
        label: r.name,
        description: r.kind,
        detail: r.filePath
          ? `${r.filePath}:${r.lineStart ?? ""}`
          : undefined,
        result: r,
      }));

      const selected = await vscode.window.showQuickPick(items, {
        placeHolder: `Results for "${query}"`,
      });

      if (selected?.result) {
        await navigateToNode(selected.result);
      }
    })
  );

  context.subscriptions.push(
    vscode.commands.registerCommand(
      "codeReviewGraph.reviewChanges",
      async () => {
        if (!sqliteReader) {
          vscode.window.showWarningMessage(
            "Code Graph: No graph database loaded."
          );
          return;
        }

        const workspaceRoot = getWorkspaceRoot();
        if (!workspaceRoot) {
          vscode.window.showErrorMessage("No workspace folder is open.");
          return;
        }

        await vscode.window.withProgress(
          {
            location: vscode.ProgressLocation.Notification,
            title: "Code Graph: Analyzing changes...",
            cancellable: false,
          },
          async () => {
            const { execFile } = await import("node:child_process");
            const { promisify } = await import("node:util");
            const execFileAsync = promisify(execFile);

            let changedFiles: string[] = [];
            try {
              const r1 = await execFileAsync(
                "git", ["diff", "--name-only", "HEAD"],
                { cwd: workspaceRoot }
              );
              const r2 = await execFileAsync(
                "git", ["diff", "--cached", "--name-only"],
                { cwd: workspaceRoot }
              );
              changedFiles = [...new Set([
                ...r1.stdout.trim().split("\n").filter(Boolean),
                ...r2.stdout.trim().split("\n").filter(Boolean),
              ])];
            } catch {
              // git not available or not a git repo
            }

            if (changedFiles.length === 0) {
              vscode.window.showInformationMessage(
                "Code Graph: No changes detected."
              );
              return;
            }

            const absFiles = changedFiles.map(f => path.join(workspaceRoot, f));
            const impact = sqliteReader!.getImpactRadius(absFiles);

            // --- Generate review guidance ---
            const guidance: string[] = [];
            const impactedFileCount = new Set(
              impact.impactedNodes.map(n => n.filePath)
            ).size;

            // Test coverage check
            const untestedFns: string[] = [];
            for (const node of impact.changedNodes) {
              if (node.kind !== "Function" || node.isTest) { continue; }
              const edges = sqliteReader!.getEdgesByTarget(node.qualifiedName);
              const hasCoverage = edges.some(e => e.kind === "TESTED_BY");
              if (!hasCoverage) {
                const out = sqliteReader!.getEdgesBySource(node.qualifiedName);
                if (!out.some(e => e.kind === "TESTED_BY")) {
                  untestedFns.push(node.name);
                }
              }
            }

            if (untestedFns.length > 0) {
              guidance.push(
                `\u26a0\ufe0f **${untestedFns.length} changed function(s) lack test coverage**: ${untestedFns.slice(0, 5).join(", ")}${untestedFns.length > 5 ? "..." : ""}`
              );
            }

            // Wide blast radius warning
            if (impactedFileCount > 10) {
              guidance.push(
                `\u26a0\ufe0f **Wide blast radius**: ${impactedFileCount} files impacted — consider splitting this change.`
              );
            }

            // Inheritance changes
            const inheritanceChanges = impact.edges.filter(
              e => e.kind === "INHERITS" || e.kind === "IMPLEMENTS"
            );
            if (inheritanceChanges.length > 0) {
              guidance.push(
                `\u26a0\ufe0f **Inheritance chain affected**: ${inheritanceChanges.length} inheritance/implementation edge(s) touched.`
              );
            }

            // Cross-file impacts
            if (impact.impactedNodes.length > 0) {
              guidance.push(
                `\u2139\ufe0f ${impact.impactedNodes.length} nodes in ${impactedFileCount} file(s) may be affected by these changes.`
              );
            }

            // Show guidance in output channel
            const channel = vscode.window.createOutputChannel("Code Graph Review", { log: true });
            channel.appendLine("# Review Guidance");
            channel.appendLine("");
            channel.appendLine(`Changed files: ${changedFiles.length}`);
            channel.appendLine(`Changed nodes: ${impact.changedNodes.length}`);
            channel.appendLine(`Impacted nodes: ${impact.impactedNodes.length}`);
            channel.appendLine(`Impacted files: ${impactedFileCount}`);
            channel.appendLine("");
            if (guidance.length > 0) {
              for (const g of guidance) { channel.appendLine(g); }
            } else {
              channel.appendLine("\u2705 No concerns detected.");
            }
            channel.show(true);

            // Also show in graph
            GraphWebviewPanel.createOrShow(
              context.extensionUri,
              sqliteReader!,
              impact
            );

            // Update SCM decorations
            if (scmDecorationProvider && sqliteReader) {
              await scmDecorationProvider.update(sqliteReader, workspaceRoot);
            }
          }
        );
      }
    )
  );
}

/**
 * Reinitialize the reader and tree providers after a graph rebuild.
 */
async function reinitialize(
  context: vscode.ExtensionContext
): Promise<void> {
  const workspaceRoot = getWorkspaceRoot();
  if (!workspaceRoot) {
    return;
  }

  const dbPath = findGraphDb(workspaceRoot);
  if (!dbPath) {
    return;
  }

  sqliteReader?.close();
  sqliteReader = new SqliteReader(dbPath);

  // Refresh tree views
  await vscode.commands.executeCommand(
    "codeReviewGraph.codeGraph.refresh"
  );
}

/**
 * Set up a FileSystemWatcher to detect changes to the graph database.
 */
function watchGraphDb(context: vscode.ExtensionContext): void {
  const watcher = vscode.workspace.createFileSystemWatcher(
    "**/.code-review-graph/graph.db"
  );

  const dbPathRef = { current: "" };
  const workspaceRoot = getWorkspaceRoot();
  if (workspaceRoot) {
    const dbPath = findGraphDb(workspaceRoot);
    if (dbPath) {
      dbPathRef.current = dbPath;
    }
  }

  watcher.onDidChange(() => {
    // Close and reopen to pick up external writes
    if (sqliteReader && dbPathRef.current) {
      sqliteReader.close();
      sqliteReader = new SqliteReader(dbPathRef.current);
      vscode.commands.executeCommand("codeReviewGraph.codeGraph.refresh");
    }
  });

  watcher.onDidCreate(async () => {
    const wsRoot = getWorkspaceRoot();
    if (wsRoot && !sqliteReader) {
      const dbPath = findGraphDb(wsRoot);
      if (dbPath) {
        dbPathRef.current = dbPath;
        sqliteReader = new SqliteReader(dbPath);
        vscode.commands.executeCommand("codeReviewGraph.codeGraph.refresh");
      }
    }
  });

  watcher.onDidDelete(() => {
    sqliteReader?.close();
    sqliteReader = undefined;
    dbPathRef.current = "";
  });

  context.subscriptions.push(watcher);
}

/**
 * Set up debounced auto-update on file save.
 */
function setupAutoUpdate(
  context: vscode.ExtensionContext,
  cli: CliWrapper
): void {
  const AUTO_UPDATE_DEBOUNCE_MS = 2000;

  const onSave = vscode.workspace.onDidSaveTextDocument(() => {
    const config = vscode.workspace.getConfiguration("codeReviewGraph");
    if (!config.get<boolean>("autoUpdate", true)) {
      return;
    }

    if (autoUpdateTimer) {
      clearTimeout(autoUpdateTimer);
    }

    autoUpdateTimer = setTimeout(async () => {
      const wsRoot = getWorkspaceRoot();
      if (!wsRoot || !sqliteReader) {
        return;
      }

      try {
        await cli.updateGraph(wsRoot);
      } catch {
        // Silently ignore update errors on save; user can manually update
      }
    }, AUTO_UPDATE_DEBOUNCE_MS);
  });

  context.subscriptions.push(onSave);
}

/**
 * Extension activation entry point.
 */
export async function activate(
  context: vscode.ExtensionContext
): Promise<void> {
  const cli = new CliWrapper();
  const installer = new Installer(cli);

  // Register walkthrough commands
  registerWalkthroughCommands(context, cli, installer);

  const workspaceRoot = getWorkspaceRoot();

  if (workspaceRoot) {
    const dbPath = findGraphDb(workspaceRoot);

    if (dbPath) {
      // Graph database found - initialize
      sqliteReader = new SqliteReader(dbPath);

      // Schema compatibility check
      const schemaWarning = sqliteReader.checkSchemaCompatibility();
      if (schemaWarning) {
        const choice = await vscode.window.showWarningMessage(
          `Code Graph: ${schemaWarning}`,
          "Rebuild Graph",
          "Dismiss"
        );
        if (choice === "Rebuild Graph") {
          await vscode.commands.executeCommand("codeReviewGraph.buildGraph");
        }
      }

      // Register tree view providers
      const codeGraphProvider = new CodeGraphTreeProvider(
        sqliteReader,
        workspaceRoot
      );
      const blastRadiusProvider = new BlastRadiusTreeProvider();
      const statsProvider = new StatsTreeProvider(sqliteReader);

      context.subscriptions.push(
        vscode.window.registerTreeDataProvider(
          "codeReviewGraph.codeGraph",
          codeGraphProvider
        ),
        vscode.window.registerTreeDataProvider(
          "codeReviewGraph.blastRadius",
          blastRadiusProvider
        ),
        vscode.window.registerTreeDataProvider(
          "codeReviewGraph.stats",
          statsProvider
        )
      );

      // Create status bar
      const statusBar = new StatusBar();
      statusBar.update(sqliteReader);
      statusBar.show();
      context.subscriptions.push(statusBar);

      // Register SCM file decoration provider
      scmDecorationProvider = new ScmDecorationProvider();
      context.subscriptions.push(
        vscode.window.registerFileDecorationProvider(scmDecorationProvider)
      );
    } else {
      // No graph database found - show welcome
      showWelcomeIfNeeded(context);
    }
  }

  // Register revealInTree command for bidirectional graph→tree sync
  context.subscriptions.push(
    vscode.commands.registerCommand(
      "codeReviewGraph.revealInTree",
      (_qualifiedName: string) => {
        // When a node is clicked in the graph, highlight it in the graph
        // The graph webview already calls this; the tree view sync relies
        // on the file navigation that nodeClicked also triggers.
        // This command is a hook for future tree reveal integration.
        GraphWebviewPanel.highlightNode(_qualifiedName);
      }
    )
  );

  // Register commands (always, even without a database)
  registerCommands(context, cli);

  // Watch for graph.db changes
  watchGraphDb(context);

  // Set up auto-update on save
  setupAutoUpdate(context, cli);
}

/**
 * Extension deactivation cleanup.
 */
export function deactivate(): void {
  if (autoUpdateTimer) {
    clearTimeout(autoUpdateTimer);
    autoUpdateTimer = undefined;
  }

  sqliteReader?.close();
  sqliteReader = undefined;
}
