#!/usr/bin/env node
"use strict";

const path = require("path");
const {
  parseConstants,
  resolveFile,
  diffClassify,
  buildChangeSet,
  applyTemplate,
  applyTemplateDir,
  verifyNoUnresolved,
  deprojectify,
  mergeConstantsSections,
  mergeSections,
} = require("./lib/template-engine");
const { EXIT_SUCCESS, EXIT_ERROR, EXIT_DRIFT } = require("./lib/constants");

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

function parseArgs(argv) {
  const args = argv.slice(2);
  const command = args[0];
  const positional = [];
  const flags = {
    values: null,
    output: null,
    format: "json",
    exclude: [],
    dryRun: false,
    quiet: false,
    includeUnchanged: false,
    strategy: null,
  };

  for (let i = 1; i < args.length; i++) {
    const arg = args[i];
    if (arg === "--values" && i + 1 < args.length) {
      flags.values = args[++i];
    } else if (arg === "--output" && i + 1 < args.length) {
      flags.output = args[++i];
    } else if (arg === "--format" && i + 1 < args.length) {
      flags.format = args[++i];
    } else if (arg === "--exclude" && i + 1 < args.length) {
      flags.exclude.push(args[++i]);
    } else if (arg === "--dry-run") {
      flags.dryRun = true;
    } else if (arg === "--quiet") {
      flags.quiet = true;
    } else if (arg === "--include-unchanged") {
      flags.includeUnchanged = true;
    } else if (arg === "--strategy" && i + 1 < args.length) {
      flags.strategy = args[++i];
    } else if (!arg.startsWith("--")) {
      positional.push(arg);
    }
  }

  return { command, positional, flags };
}

function loadValues(valuesPath) {
  if (!valuesPath) {
    console.error("Error: --values <path> is required");
    process.exit(EXIT_ERROR);
  }
  return parseConstants(path.resolve(valuesPath));
}

// ─── Commands ───────────────────────────────────────────────────────────────

function cmdParseConstants(positional) {
  const filePath = positional[0];
  if (!filePath) {
    console.error("Usage: template-cli parse-constants <path>");
    process.exit(EXIT_ERROR);
  }
  const values = parseConstants(path.resolve(filePath));
  console.log(JSON.stringify(Object.fromEntries(values), null, 2));
}

function cmdResolve(positional, flags) {
  const filePath = positional[0];
  if (!filePath) {
    console.error("Usage: template-cli resolve <file> --values <path>");
    process.exit(EXIT_ERROR);
  }
  const values = loadValues(flags.values);
  const result = resolveFile(path.resolve(filePath), values);

  if (result === null) {
    console.error("Binary file, cannot resolve");
    process.exit(EXIT_ERROR);
  }

  if (flags.output) {
    const fs = require("fs");
    fs.writeFileSync(path.resolve(flags.output), result.resolved, "utf-8");
    if (!flags.quiet) {
      console.error(
        `Resolved ${result.replacements.length} placeholder(s) → ${flags.output}`,
      );
    }
  } else if (flags.format === "json") {
    console.log(
      JSON.stringify(
        {
          replacements: result.replacements,
          unresolvedTokens: result.unresolvedTokens,
          resolved: result.resolved,
        },
        null,
        2,
      ),
    );
  } else {
    process.stdout.write(result.resolved);
  }
}

function cmdResolveDir(positional, flags) {
  const dir = positional[0];
  if (!dir) {
    console.error("Usage: template-cli resolve-dir <dir> --values <path>");
    process.exit(EXIT_ERROR);
  }
  const values = loadValues(flags.values);
  const results = applyTemplateDir(
    path.resolve(dir),
    path.resolve(dir),
    values,
    {
      dryRun: flags.dryRun,
      exclude: flags.exclude,
    },
  );

  if (flags.format === "json") {
    console.log(JSON.stringify(results, null, 2));
  } else if (!flags.quiet) {
    console.log(
      `Resolved: ${results.written} files, ${results.skipped} skipped, ${results.errors.length} errors`,
    );
  }
}

function cmdDiff(positional, flags) {
  const [templatePath, deployedPath] = positional;
  if (!templatePath || !deployedPath) {
    console.error(
      "Usage: template-cli diff <template> <deployed> --values <path>",
    );
    process.exit(EXIT_ERROR);
  }
  const values = loadValues(flags.values);
  const result = diffClassify(
    path.resolve(templatePath),
    path.resolve(deployedPath),
    values,
  );

  if (flags.format === "json") {
    console.log(JSON.stringify(result, null, 2));
  } else {
    console.log(
      `${result.classification}  template:${result.templateChecksum}  deployed:${result.deployedChecksum}`,
    );
  }
}

function cmdChangeset(positional, flags) {
  const [srcDir, tgtDir] = positional;
  if (!srcDir || !tgtDir) {
    console.error(
      "Usage: template-cli changeset <src-dir> <tgt-dir> --values <path>",
    );
    process.exit(EXIT_ERROR);
  }
  const values = loadValues(flags.values);
  const result = buildChangeSet(
    path.resolve(srcDir),
    path.resolve(tgtDir),
    values,
    {
      exclude: flags.exclude,
      includeUnchanged: flags.includeUnchanged,
    },
  );

  if (flags.format === "text") {
    console.log(`Change Set Summary:`);
    console.log(`  Added:     ${result.summary.added}`);
    console.log(`  Removed:   ${result.summary.removed}`);
    console.log(`  Modified:  ${result.summary.modified}`);
    console.log(`  Identical: ${result.summary.identical}`);
    if (result.added.length) {
      console.log(`\nAdded:`);
      for (const f of result.added) console.log(`  + ${f.relPath}`);
    }
    if (result.removed.length) {
      console.log(`\nRemoved:`);
      for (const f of result.removed) console.log(`  - ${f.relPath}`);
    }
    if (result.modified.length) {
      console.log(`\nModified:`);
      for (const f of result.modified)
        console.log(`  ~ ${f.relPath} (${f.classification})`);
    }
  } else {
    console.log(JSON.stringify(result, null, 2));
  }
}

function cmdApply(positional, flags) {
  const [templatePath, targetPath] = positional;
  if (!templatePath || !targetPath) {
    console.error(
      "Usage: template-cli apply <template> <target> --values <path>",
    );
    process.exit(EXIT_ERROR);
  }
  const values = loadValues(flags.values);
  const result = applyTemplate(
    path.resolve(templatePath),
    path.resolve(targetPath),
    values,
    { dryRun: flags.dryRun },
  );

  if (flags.format === "json") {
    console.log(JSON.stringify(result, null, 2));
  } else if (!flags.quiet) {
    console.log(
      `${flags.dryRun ? "[dry-run] " : ""}Applied ${result.replacements.length} replacement(s)`,
    );
    if (result.unresolvedTokens.length) {
      console.log(`  Unresolved tokens: ${result.unresolvedTokens.join(", ")}`);
    }
  }
}

function cmdApplyDir(positional, flags) {
  const [srcDir, tgtDir] = positional;
  if (!srcDir || !tgtDir) {
    console.error(
      "Usage: template-cli apply-dir <src-dir> <tgt-dir> --values <path>",
    );
    process.exit(EXIT_ERROR);
  }
  const values = loadValues(flags.values);
  const results = applyTemplateDir(
    path.resolve(srcDir),
    path.resolve(tgtDir),
    values,
    {
      dryRun: flags.dryRun,
      exclude: flags.exclude,
    },
  );

  if (flags.format === "json") {
    console.log(JSON.stringify(results, null, 2));
  } else if (!flags.quiet) {
    console.log(
      `${flags.dryRun ? "[dry-run] " : ""}Written: ${results.written}, Skipped: ${results.skipped}, Errors: ${results.errors.length}`,
    );
  }
}

function cmdVerify(positional, flags) {
  const dir = positional[0];
  if (!dir) {
    console.error("Usage: template-cli verify <dir>");
    process.exit(EXIT_ERROR);
  }
  const result = verifyNoUnresolved(path.resolve(dir), {
    exclude: flags.exclude.length
      ? flags.exclude
      : ["project-constants.md", "project-catalog.yaml"],
  });

  if (flags.format === "json") {
    console.log(JSON.stringify(result, null, 2));
  } else if (!flags.quiet) {
    if (result.clean) {
      console.log("No unresolved placeholders found.");
    } else {
      console.log(
        `Found unresolved placeholders in ${result.files.length} file(s):`,
      );
      for (const f of result.files) {
        console.log(`  ${f.path}: ${f.tokens.join(", ")}`);
      }
    }
  }

  process.exit(result.clean ? EXIT_SUCCESS : EXIT_DRIFT);
}

function cmdDeprojectify(positional, flags) {
  const filePath = positional[0];
  if (!filePath) {
    console.error("Usage: template-cli deprojectify <file> --values <path>");
    process.exit(EXIT_ERROR);
  }
  const values = loadValues(flags.values);
  const fs = require("fs");
  const content = fs.readFileSync(path.resolve(filePath), "utf-8");
  const result = deprojectify(content, values);

  if (flags.output) {
    fs.writeFileSync(
      path.resolve(flags.output),
      result.deprojectified,
      "utf-8",
    );
    if (!flags.quiet) {
      console.error(
        `Deprojectified ${result.replacements.length} value(s) → ${flags.output}`,
      );
    }
  } else if (flags.format === "json") {
    console.log(
      JSON.stringify(
        {
          replacements: result.replacements,
          deprojectified: result.deprojectified,
        },
        null,
        2,
      ),
    );
  } else {
    process.stdout.write(result.deprojectified);
  }
}

function cmdMergeSections(positional, flags) {
  const [templatePath, deployedPath] = positional;
  if (!templatePath || !deployedPath) {
    console.error(
      "Usage: template-cli merge-sections <template> <deployed> --strategy <markdown|constants> [--values <path>] [--output <path>]",
    );
    process.exit(EXIT_ERROR);
  }

  const strategy = flags.strategy || "markdown";
  if (strategy !== "markdown" && strategy !== "constants") {
    console.error(
      `Error: --strategy must be "markdown" or "constants", got "${strategy}"`,
    );
    process.exit(EXIT_ERROR);
  }

  const fs = require("fs");
  let templateContent = fs.readFileSync(path.resolve(templatePath), "utf-8");
  const deployedContent = fs.readFileSync(path.resolve(deployedPath), "utf-8");

  // Resolve placeholders in template if --values provided
  if (flags.values) {
    const values = loadValues(flags.values);
    const { resolved } = require("./lib/template-engine").resolveTemplate(
      templateContent,
      values,
    );
    templateContent = resolved;
  }

  let result;
  if (strategy === "constants") {
    result = mergeConstantsSections(templateContent, deployedContent);
  } else {
    result = mergeSections(templateContent, deployedContent);
  }

  if (flags.output) {
    fs.writeFileSync(path.resolve(flags.output), result.merged, "utf-8");
    if (!flags.quiet) {
      if (strategy === "constants") {
        console.error(
          `Merged: ${result.sectionsAdded} section(s) added, ${result.rowsAdded} row(s) added`,
        );
      } else {
        console.error(
          `Merged: ${result.sectionsAdded} section(s) added, ${result.sectionsPreserved} section(s) preserved`,
        );
      }
    }
  } else if (flags.format === "json") {
    console.log(JSON.stringify(result, null, 2));
  } else {
    process.stdout.write(result.merged);
  }
}

// ─── Usage ──────────────────────────────────────────────────────────────────

function showUsage() {
  console.log(`Usage: template-cli <command> [options]

Commands:
  parse-constants <path>                          Parse constants file to JSON
  resolve <file> --values <path> [--output <f>]   Resolve placeholders in file
  resolve-dir <dir> --values <path>               Resolve all files in-place
  diff <template> <deployed> --values <path>      Classify template vs deployed
  changeset <src-dir> <tgt-dir> --values <path>   Full directory comparison
  apply <template> <target> --values <path>       Copy + resolve one file
  apply-dir <src-dir> <tgt-dir> --values <path>   Recursive copy + resolve
  verify <dir>                                    Scan for unresolved tokens
  deprojectify <file> --values <path>             Reverse-resolve values
  merge-sections <tpl> <deployed> --strategy <s>  Section-level merge

Global flags:
  --dry-run            Preview without writing
  --format json|text   Output format (default: json)
  --exclude <pattern>  Exclude patterns (repeatable)
  --quiet              Suppress non-essential output
  --include-unchanged  Include unchanged files in changeset
  --output <path>      Write output to file
  --strategy <s>       Merge strategy: markdown or constants`);
}

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

const { command, positional, flags } = parseArgs(process.argv);

switch (command) {
  case "parse-constants":
    cmdParseConstants(positional);
    break;
  case "resolve":
    cmdResolve(positional, flags);
    break;
  case "resolve-dir":
    cmdResolveDir(positional, flags);
    break;
  case "diff":
    cmdDiff(positional, flags);
    break;
  case "changeset":
    cmdChangeset(positional, flags);
    break;
  case "apply":
    cmdApply(positional, flags);
    break;
  case "apply-dir":
    cmdApplyDir(positional, flags);
    break;
  case "verify":
    cmdVerify(positional, flags);
    break;
  case "deprojectify":
    cmdDeprojectify(positional, flags);
    break;
  case "merge-sections":
    cmdMergeSections(positional, flags);
    break;
  default:
    showUsage();
    process.exit(command ? EXIT_ERROR : EXIT_SUCCESS);
}
