// ── One-command Setup ──────────────────────────────────────────────
// Run via: npm run setup
// Does the following in order:
//   1. npm install (Node.js dependencies)
//   2. pip install -r requirements.txt (Python dependencies)
//   3. playwright install firefox chromium (Playwright browsers)
//   4. Copies .env.example to .env.local if not exists
//   5. --self-heal flag: runs build check + auto-generates missing modules
//
// All steps are cross-platform (Windows, Mac, Linux).

import { execSync } from "node:child_process"
import { existsSync, copyFileSync, readFileSync, writeFileSync, mkdirSync } from "node:fs"
import { platform } from "node:os"
import { resolve, dirname } from "node:path"
import { fileURLToPath } from "node:url"

// Shell command separator differs per platform (Windows uses &, POSIX uses ;)
const SEP = platform() === "win32" ? "&" : ";"
const SELF = dirname(fileURLToPath(import.meta.url))
const ROOT = resolve(SELF, "..")

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

/** Try known Python command names until one succeeds, then exit if none found. */
function detectPython() {
  const candidates = platform() === "win32" ? ["python", "python3"] : ["python3", "python"]
  for (const cmd of candidates) {
    try {
      execSync(`${cmd} --version`, { stdio: "pipe", timeout: 5000 })
      return cmd
    } catch {}
  }
  console.error("Python not found. Install Python 3 from https://python.org")
  process.exit(1)
}

/** Detect pip; fall back to `python -m pip` if no standalone pip binary exists. */
function detectPip(python) {
  const candidates = platform() === "win32" ? ["pip", "pip3"] : ["pip3", "pip"]
  for (const cmd of candidates) {
    try {
      execSync(`${cmd} --version`, { stdio: "pipe", timeout: 5000 })
      return cmd
    } catch {}
  }
  return `${python} -m pip`
}

/** Run a shell command with label output. Exits on failure unless opts.optional is set. */
function run(cmd, label, opts = {}) {
  console.log(`\n── ${label} ──`)
  try {
    execSync(cmd, { stdio: "inherit", timeout: opts.timeout || 120000, cwd: opts.cwd || ROOT })
  } catch (e) {
    if (opts.optional) {
      console.log(`  ~ Skipped (${e.message})`)
      return false
    }
    console.error(`  ✗ Failed: ${e.message}`)
    process.exit(1)
  }
  return true
}

// ── Self-Healing Module Registry ───────────────────────────────────
// Maps internal import paths (like @/data/stickers) to template files.
// Supports custom templates in .setup-templates/ that override built-ins.
// Fallback patterns catch whole directories (e.g. @/hooks/* → hook-generic.ts).

function loadRegistry() {
  const registryPath = resolve(ROOT, ".setup-templates", "registry.json")
  const templatesDir = resolve(ROOT, ".setup-templates", "templates")

  // Hard-coded templates for commonly imported modules
  const builtInTemplates = {
    "data/stickers": { template: "stickers.ts" },
    "data/constants": { template: "constants.ts" },
    "lib/utils": { template: "utils.ts" },
    "types/index": { template: "types-index.ts" },
    "hooks/use-media": { template: "hooks/use-media.ts" },
    "hooks/use-debounce": { template: "hooks/use-debounce.ts" },
    "hooks/use-local-storage": { template: "hooks/use-local-storage.ts" },
  }

  // Catch-all fallbacks for entire directory prefixes
  const builtInFallbacks = {
    "@/data/*": "data-generic.ts",
    "@/lib/*": "lib-generic.ts",
    "@/hooks/*": "hook-generic.ts",
    "@/types/*": "types-generic.ts",
    "@/components/*": "component-generic.tsx",
    "@/utils/*": "utils-generic.ts",
  }

  let templates = { ...builtInTemplates }
  let fallbacks = { ...builtInFallbacks }

  // Merge custom templates from .setup-templates/registry.json (if it exists)
  try {
    if (existsSync(registryPath)) {
      const custom = JSON.parse(readFileSync(registryPath, "utf-8"))
      templates = { ...templates, ...(custom.templates || {}) }
      fallbacks = { ...fallbacks, ...(custom.fallbackTemplates || {}) }
    }
  } catch {}

  const templateCache = {}

  /** Read a template file, checking the custom templates dir first, then hooks subdirectory. */
  function getTemplate(templateName) {
    if (templateCache[templateName]) return templateCache[templateName]
    const candidatePaths = [
      resolve(templatesDir, templateName),
      resolve(templatesDir, "hooks", templateName.replace("hooks/", "")),
    ]
    for (const p of candidatePaths) {
      if (existsSync(p)) {
        const content = readFileSync(p, "utf-8")
        templateCache[templateName] = content
        return content
      }
    }
    return ""
  }

  /** Convert a glob pattern (e.g. @/hooks/*) to a regex and test against the internal path. */
  function matchFallback(internalPath) {
    for (const [pattern, templateFile] of Object.entries(fallbacks)) {
      const regexStr = "^" + pattern
        .replace(/\//g, "\\/").replace(/\./g, "\\.")
        .replace(/\*/g, ".*")
        .replace(/[$^+(){}[\]|]/g, "\\$&") + "$"
      const regex = new RegExp(regexStr)
      if (regex.test(internalPath)) return templateFile
    }
    return null
  }

  /** Generate (or regenerate) a single module file from its template. */
  function generateModule(internalPath) {
    let templateFile = templates[internalPath]?.template
    if (!templateFile) templateFile = matchFallback(internalPath)
    if (!templateFile) return { success: false, error: `No template for @/${internalPath}` }

    const content = getTemplate(templateFile)
    if (!content) return { success: false, error: `Template file not found: ${templateFile}` }

    const ext = templateFile.endsWith(".tsx") ? ".tsx" : ".ts"
    const filePath = resolve(ROOT, "src", internalPath + ext)
    mkdirSync(dirname(filePath), { recursive: true })
    writeFileSync(filePath, content, "utf-8")
    return { success: true, filePath, message: `Generated @/${internalPath}` }
  }

  return { generateModule }
}

/** Parse `tsc --noEmit` or `npm run build` output for missing-module errors, returning unique import paths. */
function parseMissingModules(buildOutput) {
  const results = []
  const seen = new Set()

  // Pattern: error TS2307: Cannot find module '@/path' or its corresponding type declarations.
  const errorRegex = /error\s+TS\d+:\s+Cannot find module\s+'([^']+)'/g
  let match
  while ((match = errorRegex.exec(buildOutput)) !== null) {
    const importPath = match[1]
    if (!seen.has(importPath)) {
      seen.add(importPath)
      const isInternal = importPath.startsWith("@/") || importPath.startsWith("~/")
      const internalPath = isInternal ? importPath.replace(/^[@~]\//, "") : undefined
      results.push({ importPath, isInternal, internalPath })
    }
  }

  // Also catch webpack-style: Module not found: Error: Can't resolve '@/path'
  const webpackRegex = /Module not found: Error: Can't resolve\s+'([^']+)'/g
  while ((match = webpackRegex.exec(buildOutput)) !== null) {
    const importPath = match[1]
    if (!seen.has(importPath)) {
      seen.add(importPath)
      const isInternal = importPath.startsWith("@/") || importPath.startsWith("~/")
      const internalPath = isInternal ? importPath.replace(/^[@~]\//, "") : undefined
      results.push({ importPath, isInternal, internalPath })
    }
  }

  return results
}

// ── Main ───────────────────────────────────────────────────────────

// Detect Python and pip executables before running any steps
const PY = detectPython()
const PIP = detectPip(PY)

console.log("=== CoastIT CRM Setup ===\n")

// Step 1 — Install all Node.js dependencies (includes next, react, etc.)
run("npm install", "Installing Node.js dependencies")

// Step 2 — Install Python deps for the browser-use service (Playwright-based scraper)
run(`cd browser-use-service ${SEP} ${PIP} install -r requirements.txt`, "Installing Python dependencies")

// Step 3 — Download Playwright browser binaries for scraper automation
run(`${PY} -m playwright install firefox chromium`, "Installing Playwright browsers")

// Step 4 — Bootstrap .env.local from the example template (won't overwrite existing)
if (!existsSync(resolve(ROOT, ".env.local"))) {
  console.log("\n── Creating .env.local ──")
  copyFileSync(resolve(ROOT, ".env.example"), resolve(ROOT, ".env.local"))
  console.log("  ✓ Created .env.local from .env.example — edit it with your settings")
} else {
  console.log("\n── .env.local already exists, skipping ──")
}

// Step 5 — Print post-setup instructions
console.log("\n── Final Steps ──")
console.log("  1. Make sure PostgreSQL is running with database 'crm'")
console.log("  2. Pull the Ollama model:  ollama pull dolphin-llama3:8b")
console.log("  3. Edit .env.local with your settings")
console.log("  4. Run:  npm run db:migrate (apply database migrations)")
console.log("  5. Run:  npm run dev")
console.log("\n=== Setup complete! ===")
