#!/usr/bin/env node

"use strict";

/**
 * migrate-local-issues.js — Convert markdown local issues to tasks.json
 *
 * Reads markdown issue files from docs/issues/ (YAML frontmatter format) and
 * creates corresponding entries in .claude/tasks.json via manage-tasks.js.
 *
 * Usage:
 *   node migrate-local-issues.js [--issues-dir <path>] [--dry-run] [--verbose]
 *
 * Options:
 *   --issues-dir <path>  Override the issues directory (default: docs/issues)
 *   --dry-run            Parse and report without writing to tasks.json
 *   --verbose            Print per-file details to stderr
 *
 * Output (stdout, JSON):
 *   { "migrated": N, "skipped": N, "errors": N }
 *
 * Exit codes:
 *   0  Success (even if some files skipped or errored)
 *   1  Fatal error (manage-tasks.js not found, issues dir not found, etc.)
 */

const fs = require("fs");
const path = require("path");
const { execFileSync } = require("child_process");

// ─── Argument Parsing ─────────────────────────────────────────────────────────

const args = process.argv.slice(2);

function parseFlag(flagArgs, flag) {
  const idx = flagArgs.indexOf(flag);
  return idx >= 0 && flagArgs[idx + 1] ? flagArgs[idx + 1] : null;
}

function hasFlag(flagArgs, flag) {
  return flagArgs.includes(flag);
}

const dryRun = hasFlag(args, "--dry-run");
const verbose = hasFlag(args, "--verbose");
const issuesDirOverride = parseFlag(args, "--issues-dir");

// ─── Project Root Discovery ───────────────────────────────────────────────────

/**
 * Walk up from cwd to find the directory containing .claude/.
 * Returns the project root or null if not found.
 */
function findProjectRoot() {
  if (process.env.CLAUDE_PROJECT_DIR) {
    return process.env.CLAUDE_PROJECT_DIR;
  }
  let dir = process.cwd();
  while (true) {
    const claudeDir = path.join(dir, ".claude");
    if (fs.existsSync(claudeDir) && fs.statSync(claudeDir).isDirectory()) {
      return dir;
    }
    const parent = path.dirname(dir);
    if (parent === dir) return null;
    dir = parent;
  }
}

// ─── Locate manage-tasks.js ──────────────────────────────────────────────────

/**
 * Find manage-tasks.js relative to this script or on PATH.
 * Checks sibling location first (co-deployed in scripts/).
 */
function findManageTasks() {
  const sibling = path.join(__dirname, "manage-tasks.js");
  if (fs.existsSync(sibling)) return sibling;

  // Fallback: search project .claude/scripts/
  const projectRoot = findProjectRoot();
  if (projectRoot) {
    const candidate = path.join(
      projectRoot,
      ".claude",
      "scripts",
      "manage-tasks.js",
    );
    if (fs.existsSync(candidate)) return candidate;
  }

  return null;
}

// ─── YAML Frontmatter Parser ──────────────────────────────────────────────────

/**
 * Parse a minimal YAML frontmatter block from markdown content.
 * Handles the scalar types used in issue files:
 *   - Quoted strings: title: "value"
 *   - Unquoted strings: status: open
 *   - Arrays: labels: [bug, enhancement] or multiline block sequences
 *   - Empty string: assignee: ""
 *
 * Returns { frontmatter: Object, body: string } where body is the markdown
 * content after the closing --- delimiter.
 */
function parseFrontmatter(content) {
  const lines = content.split("\n");

  // Must start with ---
  if (lines[0].trim() !== "---") {
    return { frontmatter: null, body: content };
  }

  // Find closing ---
  let closeIdx = -1;
  for (let i = 1; i < lines.length; i++) {
    if (lines[i].trim() === "---") {
      closeIdx = i;
      break;
    }
  }

  if (closeIdx === -1) {
    return { frontmatter: null, body: content };
  }

  const fmLines = lines.slice(1, closeIdx);
  const body = lines
    .slice(closeIdx + 1)
    .join("\n")
    .trimStart();

  const fm = {};
  let i = 0;
  while (i < fmLines.length) {
    const line = fmLines[i];
    // Skip blank lines
    if (!line.trim()) {
      i++;
      continue;
    }

    const colonIdx = line.indexOf(":");
    if (colonIdx === -1) {
      i++;
      continue;
    }

    const key = line.slice(0, colonIdx).trim();
    const rawVal = line.slice(colonIdx + 1).trim();

    // Inline array: labels: [bug, enhancement]
    if (rawVal.startsWith("[")) {
      const inner = rawVal.slice(1, rawVal.lastIndexOf("]"));
      fm[key] =
        inner.trim() === ""
          ? []
          : inner
              .split(",")
              .map((s) => s.trim().replace(/^["']|["']$/g, ""))
              .filter(Boolean);
      i++;
      continue;
    }

    // Block sequence: next lines start with "  - item"
    if (rawVal === "") {
      const items = [];
      i++;
      while (i < fmLines.length && /^\s+-\s/.test(fmLines[i])) {
        items.push(fmLines[i].replace(/^\s+-\s*/, "").trim());
        i++;
      }
      fm[key] = items;
      continue;
    }

    // Quoted string
    if (
      (rawVal.startsWith('"') && rawVal.endsWith('"')) ||
      (rawVal.startsWith("'") && rawVal.endsWith("'"))
    ) {
      fm[key] = rawVal.slice(1, -1);
      i++;
      continue;
    }

    // Unquoted scalar
    fm[key] = rawVal;
    i++;
  }

  return { frontmatter: fm, body };
}

// ─── Markdown Section Extractor ───────────────────────────────────────────────

/**
 * Extract the content of a named ## section from markdown body.
 * Returns the trimmed text between the section heading and the next ## heading
 * (or end of string).
 */
function extractSection(body, sectionName) {
  const re = new RegExp(`^##\\s+${sectionName}\\s*$`, "im");
  const match = re.exec(body);
  if (!match) return "";

  const start = match.index + match[0].length;
  const remainder = body.slice(start);

  // Stop at next ## heading
  const nextHeading = /^##\s/m.exec(remainder);
  const raw = nextHeading ? remainder.slice(0, nextHeading.index) : remainder;

  return raw.trim();
}

// ─── Status Mapping ───────────────────────────────────────────────────────────

// Map old markdown issue statuses to tasks.json valid statuses
const STATUS_MAP = {
  open: "open",
  "in-progress": "in-progress",
  "in progress": "in-progress",
  done: "done",
  closed: "closed",
  resolved: "done",
};

function mapStatus(raw) {
  if (!raw) return "open";
  const normalized = raw.toLowerCase().trim();
  return STATUS_MAP[normalized] || "open";
}

// ─── Priority Mapping ─────────────────────────────────────────────────────────

const VALID_PRIORITIES = ["low", "medium", "high", "urgent"];

function mapPriority(raw) {
  if (!raw) return "medium";
  const normalized = raw.toLowerCase().trim();
  return VALID_PRIORITIES.includes(normalized) ? normalized : "medium";
}

// ─── Migration ────────────────────────────────────────────────────────────────

function log(msg) {
  if (verbose) process.stderr.write(msg + "\n");
}

function fatal(msg) {
  process.stderr.write(`[FATAL] ${msg}\n`);
  process.exit(1);
}

function main() {
  const projectRoot = findProjectRoot();
  if (!projectRoot) {
    fatal(
      "Could not locate project root (.claude/ directory not found). " +
        "Run from within an ECK project or set CLAUDE_PROJECT_DIR.",
    );
  }

  const issuesDir = issuesDirOverride
    ? path.resolve(issuesDirOverride)
    : path.join(projectRoot, "docs", "issues");

  if (!fs.existsSync(issuesDir)) {
    fatal(
      `Issues directory not found: ${issuesDir}\n` +
        "Pass --issues-dir <path> to specify a custom location.",
    );
  }

  const manageTasksPath = findManageTasks();
  if (!manageTasksPath && !dryRun) {
    fatal(
      "manage-tasks.js not found. Expected alongside this script in scripts/.",
    );
  }

  // Collect markdown issue files
  const entries = fs.readdirSync(issuesDir).filter((f) => f.endsWith(".md"));

  if (entries.length === 0) {
    log(`No markdown files found in ${issuesDir}`);
    console.log(JSON.stringify({ migrated: 0, skipped: 0, errors: 0 }));
    return;
  }

  log(`Found ${entries.length} markdown file(s) in ${issuesDir}`);

  let migrated = 0;
  let skipped = 0;
  let errors = 0;

  for (const filename of entries) {
    const filePath = path.join(issuesDir, filename);
    const issueId = filename.replace(/\.md$/, "");

    let content;
    try {
      content = fs.readFileSync(filePath, "utf-8");
    } catch (err) {
      process.stderr.write(`[ERROR] Cannot read ${filename}: ${err.message}\n`);
      errors++;
      continue;
    }

    const { frontmatter, body } = parseFrontmatter(content);

    if (!frontmatter) {
      process.stderr.write(
        `[SKIP] ${filename}: No valid YAML frontmatter found\n`,
      );
      skipped++;
      continue;
    }

    const title = frontmatter.title;
    if (!title || !title.trim()) {
      process.stderr.write(
        `[SKIP] ${filename}: Missing or empty title in frontmatter\n`,
      );
      skipped++;
      continue;
    }

    const status = mapStatus(frontmatter.status);
    const priority = mapPriority(frontmatter.priority);
    const description = extractSection(body, "Description");

    // Build labels array: frontmatter.labels may already be an array or string
    let labels = [];
    if (Array.isArray(frontmatter.labels)) {
      labels = frontmatter.labels.filter(Boolean);
    } else if (typeof frontmatter.labels === "string" && frontmatter.labels) {
      labels = [frontmatter.labels];
    }

    log(
      `Processing ${issueId}: "${title}" status=${status} priority=${priority} labels=[${labels.join(",")}]`,
    );

    if (dryRun) {
      process.stderr.write(
        `[DRY-RUN] Would create task for ${issueId}: "${title}"\n`,
      );
      migrated++;
      continue;
    }

    // Build args for manage-tasks.js create
    const createArgs = [manageTasksPath, "create", title];

    if (priority !== "medium") {
      createArgs.push("--priority", priority);
    }

    if (description) {
      createArgs.push("--description", description);
    }

    if (labels.length > 0) {
      // manage-tasks.js create does not have a --labels flag directly;
      // labels are set via update after creation.
    }

    // Create the task
    let createOutput;
    try {
      createOutput = execFileSync(process.execPath, createArgs, {
        encoding: "utf-8",
        cwd: projectRoot,
        env: {
          ...process.env,
          CLAUDE_PROJECT_DIR: projectRoot,
        },
      });
    } catch (err) {
      process.stderr.write(
        `[ERROR] Failed to create task for ${issueId} ("${title}"): ${err.message}\n`,
      );
      if (err.stderr) process.stderr.write(err.stderr);
      errors++;
      continue;
    }

    let created;
    try {
      created = JSON.parse(createOutput.trim());
    } catch {
      process.stderr.write(
        `[ERROR] Unexpected output from manage-tasks.js for ${issueId}: ${createOutput}\n`,
      );
      errors++;
      continue;
    }

    const taskId = created.id;
    log(`  Created task ${taskId} for ${issueId}`);

    // Transition to the correct status if not "open"
    if (status !== "open") {
      try {
        execFileSync(
          process.execPath,
          [manageTasksPath, "transition", taskId, status],
          {
            encoding: "utf-8",
            cwd: projectRoot,
            env: {
              ...process.env,
              CLAUDE_PROJECT_DIR: projectRoot,
            },
          },
        );
        log(`  Transitioned ${taskId} to ${status}`);
      } catch (err) {
        process.stderr.write(
          `[WARN] Could not set status "${status}" on ${taskId} (${issueId}): ${err.message}\n`,
        );
        // Non-fatal: task was created, just not in the right status
      }
    }

    migrated++;
  }

  const result = { migrated, skipped, errors };
  console.log(JSON.stringify(result));
}

main();
