/**
 * SF Workflow Templates — Registry & Resolution
 *
 * Loads the workflow template registry and resolves templates by name,
 * alias, or trigger-keyword matching against user input.
 */

import { readFileSync, existsSync } from "node:fs";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
import { homedir } from "node:os";

const __extensionDir = resolveGsdExtensionDir();
const registryPath = join(__extensionDir, "workflow-templates", "registry.json");

/** Resolve the SF extension dir with fallback to ~/.sf/agent/extensions/sf/. */
function resolveGsdExtensionDir(): string {
  const moduleDir = dirname(fileURLToPath(import.meta.url));
  if (existsSync(join(moduleDir, "workflow-templates"))) return moduleDir;
  const sfHome = process.env.SF_HOME || join(homedir(), ".sf");
  const agentGsdDir = join(sfHome, "agent", "extensions", "sf");
  if (existsSync(join(agentGsdDir, "workflow-templates"))) return agentGsdDir;
  return moduleDir;
}

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

export interface TemplateEntry {
  name: string;
  description: string;
  file: string;
  phases: string[];
  triggers: string[];
  artifact_dir: string | null;
  estimated_complexity: string;
  requires_project: boolean;
}

export interface TemplateRegistry {
  version: number;
  templates: Record<string, TemplateEntry>;
}

export interface TemplateMatch {
  id: string;
  template: TemplateEntry;
  confidence: "exact" | "high" | "medium" | "low";
  matchedTrigger?: string;
}

export interface MilestoneTemplateSliceScaffold {
  sliceId: string;
  title: string;
  risk: string;
  depends: string[];
  demo: string;
  goal: string;
  successCriteria: string;
  proofLevel: string;
  integrationClosure: string;
  observabilityImpact: string;
}

// ─── Registry Cache ──────────────────────────────────────────────────────────

let cachedRegistry: TemplateRegistry | null = null;

/**
 * Load and cache the workflow template registry.
 */
export function loadRegistry(): TemplateRegistry {
  if (cachedRegistry) return cachedRegistry;

  if (!existsSync(registryPath)) {
    cachedRegistry = { version: 1, templates: {} };
    return cachedRegistry;
  }

  try {
    const content = readFileSync(registryPath, "utf-8");
    cachedRegistry = JSON.parse(content) as TemplateRegistry;
  } catch {
    cachedRegistry = { version: 1, templates: {} };
  }
  return cachedRegistry;
}

/**
 * Resolve a template by exact name or alias.
 * Returns null if no match found.
 */
export function resolveByName(nameOrAlias: string): TemplateMatch | null {
  const registry = loadRegistry();
  const normalized = nameOrAlias.toLowerCase().trim();

  // Exact key match
  if (registry.templates[normalized]) {
    return {
      id: normalized,
      template: registry.templates[normalized],
      confidence: "exact",
    };
  }

  // Match by template name (case-insensitive)
  for (const [id, entry] of Object.entries(registry.templates)) {
    if (entry.name.toLowerCase() === normalized) {
      return { id, template: entry, confidence: "exact" };
    }
  }

  // Fuzzy: prefix match on id
  for (const [id, entry] of Object.entries(registry.templates)) {
    if (id.startsWith(normalized) || normalized.startsWith(id)) {
      return { id, template: entry, confidence: "high" };
    }
  }

  // Common aliases
  const aliases: Record<string, string> = {
    "bug": "bugfix",
    "fix": "bugfix",
    "feature": "small-feature",
    "feat": "small-feature",
    "research": "spike",
    "investigate": "spike",
    "hot": "hotfix",
    "urgent": "hotfix",
    "security": "security-audit",
    "audit": "security-audit",
    "upgrade": "dep-upgrade",
    "deps": "dep-upgrade",
    "update-deps": "dep-upgrade",
    "migration": "refactor",
    "project": "full-project",
    "full": "full-project",
  };

  const aliasMatch = aliases[normalized];
  if (aliasMatch && registry.templates[aliasMatch]) {
    return {
      id: aliasMatch,
      template: registry.templates[aliasMatch],
      confidence: "high",
    };
  }

  return null;
}

/**
 * Auto-detect the best template based on user description text.
 * Returns ranked matches sorted by confidence.
 */
export function autoDetect(description: string): TemplateMatch[] {
  const registry = loadRegistry();
  const lower = description.toLowerCase();
  const words = lower.split(/\s+/);
  const matches: TemplateMatch[] = [];

  for (const [id, entry] of Object.entries(registry.templates)) {
    let bestScore = 0;
    let bestTrigger = "";

    for (const trigger of entry.triggers) {
      const triggerLower = trigger.toLowerCase();

      // Exact phrase match in description
      if (lower.includes(triggerLower)) {
        const score = triggerLower.split(/\s+/).length * 2; // multi-word triggers score higher
        if (score > bestScore) {
          bestScore = score;
          bestTrigger = trigger;
        }
        continue;
      }

      // Single-word trigger match against description words
      if (!triggerLower.includes(" ") && words.includes(triggerLower)) {
        if (1 > bestScore) {
          bestScore = 1;
          bestTrigger = trigger;
        }
      }
    }

    if (bestScore > 0) {
      const confidence = bestScore >= 4 ? "high" : bestScore >= 2 ? "medium" : "low";
      matches.push({
        id,
        template: entry,
        confidence,
        matchedTrigger: bestTrigger,
      });
    }
  }

  // Sort by confidence (high > medium > low), then alphabetically
  const order = { exact: 0, high: 1, medium: 2, low: 3 };
  matches.sort((a, b) => order[a.confidence] - order[b.confidence] || a.id.localeCompare(b.id));

  return matches;
}

/**
 * List all templates as formatted text for display.
 */
export function listTemplates(): string {
  const registry = loadRegistry();
  const lines: string[] = ["Workflow Templates\n"];

  for (const [id, entry] of Object.entries(registry.templates)) {
    const phases = entry.phases.join(" → ");
    const complexity = entry.estimated_complexity;
    lines.push(`  ${id.padEnd(16)} ${entry.name}`);
    lines.push(`  ${"".padEnd(16)} ${entry.description}`);
    lines.push(`  ${"".padEnd(16)} Phases: ${phases}  |  Complexity: ${complexity}`);
    lines.push("");
  }

  lines.push("Usage: /sf start <template> [description]");
  lines.push("       /sf templates info <name>");

  return lines.join("\n");
}

/**
 * Get detailed info about a specific template.
 */
export function getTemplateInfo(name: string): string | null {
  const match = resolveByName(name);
  if (!match) return null;

  const { id, template: t } = match;
  const lines = [
    `Template: ${t.name} (${id})`,
    "",
    `Description: ${t.description}`,
    `Complexity:  ${t.estimated_complexity}`,
    `Requires .sf/: ${t.requires_project ? "yes" : "no"}`,
    "",
    "Phases:",
    ...t.phases.map((p, i) => `  ${i + 1}. ${p}`),
    "",
    "Triggers:",
    `  ${t.triggers.join(", ")}`,
  ];

  if (t.artifact_dir) {
    lines.push("", `Artifacts: ${t.artifact_dir}`);
  }

  const templateFilePath = join(__extensionDir, "workflow-templates", t.file);
  if (existsSync(templateFilePath)) {
    lines.push("", "Template file: loaded");
  } else {
    lines.push("", "Template file: not yet created");
  }

  return lines.join("\n");
}

/**
 * Load the raw content of a workflow template .md file.
 */
export function loadWorkflowTemplate(templateId: string): string | null {
  const match = resolveByName(templateId);
  if (!match) return null;

  const filePath = join(__extensionDir, "workflow-templates", match.template.file);
  if (!existsSync(filePath)) return null;

  return readFileSync(filePath, "utf-8");
}

export function scaffoldMilestoneSlices(templateId: string): MilestoneTemplateSliceScaffold[] | null {
  const match = resolveByName(templateId);
  if (!match) return null;

  switch (match.id) {
    case "bugfix":
    case "hotfix":
      return [
        {
          sliceId: "S01",
          title: "Reproduce and bound the failure",
          risk: "high",
          depends: [],
          demo: "The team can reproduce the failure and point to the exact broken boundary.",
          goal: "Capture the failing boundary, root-cause scope, and the contract that proves the bug is real.",
          successCriteria: "A concrete repro path and failing proof exist before any fix is treated as valid.",
          proofLevel: "runtime repro",
          integrationClosure: "The same repro path is available for post-fix verification.",
          observabilityImpact: "Logs, traces, or repro notes make the failure boundary explicit.",
        },
        {
          sliceId: "S02",
          title: "Implement the fix",
          risk: "medium",
          depends: ["S01"],
          demo: "The broken behavior is corrected without widening scope.",
          goal: "Change the implementation to satisfy the failing contract and remove the root cause.",
          successCriteria: "The contract test passes and the fix path is minimal and purpose-aligned.",
          proofLevel: "contract + integration",
          integrationClosure: "Callers continue to work on the corrected boundary with no workaround branch left behind.",
          observabilityImpact: "Failures surface clearly if the same path regresses.",
        },
        {
          sliceId: "S03",
          title: "Verify and guard against regression",
          risk: "low",
          depends: ["S02"],
          demo: "The solved boundary is proven end-to-end and the fix stays in place.",
          goal: "Re-run the repro, add regression coverage, and verify the shipped behavior.",
          successCriteria: "The original failure path is green and regression evidence is captured.",
          proofLevel: "runtime + regression",
          integrationClosure: "Verification proves the real consumer path, not just a unit-level surrogate.",
          observabilityImpact: "Follow-up diagnostics and regression checks are documented.",
        },
      ];
    case "refactor":
      return [
        {
          sliceId: "S01",
          title: "Map the current boundary",
          risk: "medium",
          depends: [],
          demo: "The team can explain what is being reshaped and what must not regress.",
          goal: "Capture current responsibilities, callers, and the contract that must survive the refactor.",
          successCriteria: "The pre-refactor contract and blast radius are explicit before code movement starts.",
          proofLevel: "contract inventory",
          integrationClosure: "Downstream boundaries and unchanged behavior are named up front.",
          observabilityImpact: "Audit notes capture the before-state and likely regression surfaces.",
        },
        {
          sliceId: "S02",
          title: "Restructure the implementation",
          risk: "medium",
          depends: ["S01"],
          demo: "The code is reorganized into the intended shape without changing the consumer contract.",
          goal: "Apply the structural change while preserving verified behavior.",
          successCriteria: "The code layout improves and the existing contract stays green.",
          proofLevel: "contract + quality",
          integrationClosure: "Callers, imports, and runtime paths remain coherent after the move.",
          observabilityImpact: "Lint/type/test failures clearly point to missed migration edges.",
        },
        {
          sliceId: "S03",
          title: "Stabilize and verify",
          risk: "low",
          depends: ["S02"],
          demo: "The new structure is validated and safe to build on.",
          goal: "Prove the refactor is complete, regression-safe, and simpler to extend.",
          successCriteria: "Quality gates and targeted regression checks pass on the new structure.",
          proofLevel: "quality + regression",
          integrationClosure: "No stale compatibility shims or duplicate paths remain without reason.",
          observabilityImpact: "Future maintainers can see the intended shape and the critical checks.",
        },
      ];
    case "small-feature":
      return [
        {
          sliceId: "S01",
          title: "Define the user-facing contract",
          risk: "medium",
          depends: [],
          demo: "The feature outcome, acceptance path, and boundary are clear before implementation.",
          goal: "State what the feature must do, for whom, and how success will be verified.",
          successCriteria: "A concrete contract exists for the intended feature behavior.",
          proofLevel: "contract",
          integrationClosure: "The consumer path and integration boundary are identified before code is written.",
          observabilityImpact: "Acceptance criteria and diagnostics are explicit.",
        },
        {
          sliceId: "S02",
          title: "Implement the core behavior",
          risk: "medium",
          depends: ["S01"],
          demo: "The feature works on its main path.",
          goal: "Build the smallest complete implementation that satisfies the agreed contract.",
          successCriteria: "The main feature path works and the contract test passes.",
          proofLevel: "contract + integration",
          integrationClosure: "Primary callers and dependent boundaries work together on the new path.",
          observabilityImpact: "Failures in the feature path are diagnosable.",
        },
        {
          sliceId: "S03",
          title: "Polish, verify, and close",
          risk: "low",
          depends: ["S02"],
          demo: "The feature is verified, documented, and safe to hand over.",
          goal: "Finish verification, edge handling, and any required docs or follow-up notes.",
          successCriteria: "The feature is truthfully ready for users or the next milestone.",
          proofLevel: "verification",
          integrationClosure: "The end-to-end user path is proven and any gaps are recorded explicitly.",
          observabilityImpact: "Tests/docs/notes make the feature supportable.",
        },
      ];
    default:
      return null;
  }
}
