import {
  init_dist
} from "./chunk-4DWOMGUM.js";
import {
  Err,
  Ok
} from "./chunk-RL4VMEXL.js";

// src/templates/engine.ts
init_dist();
import * as fs from "fs";
import * as path from "path";
import Handlebars from "handlebars";

// src/templates/schema.ts
import { z } from "zod";
var LanguageEnum = z.enum(["typescript", "python", "go", "rust", "java"]);
var ToolingSchema = z.object({
  packageManager: z.string().optional(),
  linter: z.string().optional(),
  formatter: z.string().optional(),
  buildTool: z.string().optional(),
  testRunner: z.string().optional(),
  lockFile: z.string().optional()
});
var DetectPatternSchema = z.object({
  file: z.string(),
  contains: z.string().optional()
});
var MergeStrategySchema = z.object({
  json: z.enum(["deep-merge", "overlay-wins"]).default("deep-merge"),
  files: z.literal("overlay-wins").default("overlay-wins")
});
var TemplateMetadataSchema = z.object({
  name: z.string(),
  description: z.string(),
  level: z.enum(["basic", "intermediate", "advanced"]).optional(),
  framework: z.string().optional(),
  extends: z.string().optional(),
  mergeStrategy: MergeStrategySchema.default({}),
  version: z.literal(1),
  language: LanguageEnum.optional(),
  tooling: ToolingSchema.optional(),
  detect: z.array(DetectPatternSchema).optional()
});

// src/templates/merger.ts
function isPlainObject(val) {
  return typeof val === "object" && val !== null && !Array.isArray(val);
}
function deepMergeJson(base, overlay) {
  const result = { ...base };
  for (const key of Object.keys(overlay)) {
    if (isPlainObject(result[key]) && isPlainObject(overlay[key])) {
      result[key] = deepMergeJson(
        result[key],
        overlay[key]
      );
    } else {
      result[key] = overlay[key];
    }
  }
  return result;
}
var CONCAT_KEYS = /* @__PURE__ */ new Set(["dependencies", "devDependencies", "peerDependencies"]);
function mergePackageJson(base, overlay) {
  const result = { ...base };
  for (const key of Object.keys(overlay)) {
    if (CONCAT_KEYS.has(key) && isPlainObject(result[key]) && isPlainObject(overlay[key])) {
      result[key] = {
        ...result[key],
        ...overlay[key]
      };
    } else if (isPlainObject(result[key]) && isPlainObject(overlay[key])) {
      result[key] = deepMergeJson(
        result[key],
        overlay[key]
      );
    } else {
      result[key] = overlay[key];
    }
  }
  return result;
}

// src/templates/engine.ts
var NON_JSON_PACKAGE_CONFIGS = /* @__PURE__ */ new Set([
  "pyproject.toml",
  "go.mod",
  "Cargo.toml",
  "pom.xml",
  "build.gradle",
  "build.gradle.kts"
]);
var HARNESS_CONFIG_FILES = /* @__PURE__ */ new Set(["harness.config.json", "AGENTS.md", ".harness/.gitignore"]);
function isHarnessConfigFile(relativePath) {
  return HARNESS_CONFIG_FILES.has(relativePath);
}
var PROJECT_MARKERS = [
  "package.json",
  "pom.xml",
  "build.gradle",
  "build.gradle.kts",
  "pyproject.toml",
  "setup.py",
  "requirements.txt",
  "Pipfile",
  "go.mod",
  "Cargo.toml",
  "Makefile",
  "CMakeLists.txt",
  "meson.build"
];
function scoreDetectPatterns(targetDir, patterns) {
  let score = 0;
  for (const pattern of patterns) {
    const filePath = path.join(targetDir, pattern.file);
    if (!fs.existsSync(filePath)) continue;
    if (!pattern.contains) {
      score++;
      continue;
    }
    const fd = fs.openSync(filePath, "r");
    try {
      const buf = Buffer.alloc(65536);
      const bytesRead = fs.readSync(fd, buf, 0, 65536, 0);
      const content = buf.toString("utf-8", 0, bytesRead);
      if (content.includes(pattern.contains)) score++;
    } finally {
      fs.closeSync(fd);
    }
  }
  return score;
}
function applyLanguageDefaults(context) {
  return {
    ...context,
    ...context.language === "python" && context.pythonMinVersion === void 0 && { pythonMinVersion: "3.10" },
    ...context.language === "go" && context.goModulePath === void 0 && {
      goModulePath: `github.com/example/${context.projectName}`
    },
    ...context.language === "java" && context.javaGroupId === void 0 && {
      javaGroupId: `com.example.${context.projectName.replace(/[^a-zA-Z0-9]/g, "").toLowerCase()}`
    },
    ...context.language === "rust" && context.rustEdition === void 0 && { rustEdition: "2021" }
  };
}
function mergeJsonBuffers(jsonBuffers) {
  try {
    const results = [];
    for (const [outputPath, jsons] of jsonBuffers) {
      let merged = {};
      for (const json of jsons) {
        merged = outputPath === "package.json" ? mergePackageJson(merged, json) : deepMergeJson(merged, json);
      }
      results.push({ relativePath: outputPath, content: JSON.stringify(merged, null, 2) });
    }
    return Ok(results);
  } catch (error) {
    const msg = error instanceof Error ? error.message : String(error);
    return Err(new Error(`JSON merge failed: ${msg}`));
  }
}
var TemplateEngine = class {
  constructor(templatesDir) {
    this.templatesDir = templatesDir;
  }
  templatesDir;
  listTemplates() {
    try {
      const entries = fs.readdirSync(this.templatesDir, { withFileTypes: true });
      const templates = [];
      for (const entry of entries) {
        if (!entry.isDirectory()) continue;
        const metaPath = path.join(this.templatesDir, entry.name, "template.json");
        if (!fs.existsSync(metaPath)) continue;
        const raw = JSON.parse(fs.readFileSync(metaPath, "utf-8"));
        const parsed = TemplateMetadataSchema.safeParse(raw);
        if (parsed.success) templates.push(parsed.data);
      }
      return Ok(templates);
    } catch (error) {
      return Err(
        new Error(
          `Failed to list templates: ${error instanceof Error ? error.message : String(error)}`
        )
      );
    }
  }
  resolveTemplate(level, framework, language) {
    if (language && language !== "typescript") {
      return this.resolveLanguageTemplate(language, framework);
    }
    if (!level) {
      return Err(new Error("Level is required for TypeScript/JavaScript templates"));
    }
    const levelDir = this.findTemplateDir(level, "level");
    if (!levelDir) return Err(new Error(`Template not found for level: ${level}`));
    const metaPath = path.join(levelDir, "template.json");
    const metaRaw = JSON.parse(fs.readFileSync(metaPath, "utf-8"));
    const metaResult = TemplateMetadataSchema.safeParse(metaRaw);
    if (!metaResult.success)
      return Err(new Error(`Invalid template.json in ${level}: ${metaResult.error.message}`));
    const metadata = metaResult.data;
    let files = [];
    if (metadata.extends) {
      const baseDir = path.join(this.templatesDir, metadata.extends);
      if (fs.existsSync(baseDir)) files = this.collectFiles(baseDir, metadata.extends);
    }
    const levelFiles = this.collectFiles(levelDir, level);
    files = this.mergeFileLists(files, levelFiles);
    let overlayMetadata;
    if (framework) {
      const frameworkDir = this.findTemplateDir(framework, "framework");
      if (!frameworkDir) return Err(new Error(`Framework template not found: ${framework}`));
      const fMetaPath = path.join(frameworkDir, "template.json");
      const fMetaRaw = JSON.parse(fs.readFileSync(fMetaPath, "utf-8"));
      const fMetaResult = TemplateMetadataSchema.safeParse(fMetaRaw);
      if (fMetaResult.success) overlayMetadata = fMetaResult.data;
      const frameworkFiles = this.collectFiles(frameworkDir, framework);
      files = this.mergeFileLists(files, frameworkFiles);
    }
    files = files.filter((f) => f.relativePath !== "template.json");
    const resolved = { metadata, files };
    if (overlayMetadata !== void 0) resolved.overlayMetadata = overlayMetadata;
    return Ok(resolved);
  }
  render(template, context) {
    const effectiveContext = applyLanguageDefaults(context);
    const rendered = [];
    const jsonBuffers = /* @__PURE__ */ new Map();
    for (const file of template.files) {
      const result = this.renderFile(file, effectiveContext, jsonBuffers);
      if (!result.ok) return result;
      if (result.value) rendered.push(result.value);
    }
    const mergeResult = mergeJsonBuffers(jsonBuffers);
    if (!mergeResult.ok) return mergeResult;
    rendered.push(...mergeResult.value);
    return Ok({ files: rendered });
  }
  renderFile(file, context, jsonBuffers) {
    const outputPath = file.relativePath.replace(/\.hbs$/, "");
    try {
      if (file.isHandlebars) {
        const raw = fs.readFileSync(file.absolutePath, "utf-8");
        const compiled = Handlebars.compile(raw, { strict: true });
        const content2 = compiled(context);
        if (outputPath.endsWith(".json") && file.relativePath.endsWith(".json.hbs")) {
          if (!jsonBuffers.has(outputPath)) jsonBuffers.set(outputPath, []);
          jsonBuffers.get(outputPath).push(JSON.parse(content2));
          return Ok(null);
        }
        return Ok({ relativePath: outputPath, content: content2 });
      }
      const content = fs.readFileSync(file.absolutePath, "utf-8");
      return Ok({ relativePath: file.relativePath, content });
    } catch (error) {
      const msg = error instanceof Error ? error.message : String(error);
      return Err(
        new Error(`Template render failed in ${file.sourceTemplate}/${file.relativePath}: ${msg}`)
      );
    }
  }
  write(files, targetDir, options) {
    try {
      const written = [];
      const skippedConfigs = [];
      const isNonJsLanguage = options.language && options.language !== "typescript";
      for (const file of files.files) {
        const targetPath = path.join(targetDir, file.relativePath);
        const dir = path.dirname(targetPath);
        if (!options.overwrite && options.existingProject && !isHarnessConfigFile(file.relativePath)) {
          continue;
        }
        if (!options.overwrite && isNonJsLanguage && NON_JSON_PACKAGE_CONFIGS.has(file.relativePath) && fs.existsSync(targetPath)) {
          skippedConfigs.push(file.relativePath);
          continue;
        }
        if (!options.overwrite && fs.existsSync(targetPath)) continue;
        fs.mkdirSync(dir, { recursive: true });
        fs.writeFileSync(targetPath, file.content);
        written.push(file.relativePath);
      }
      return Ok({ written, skippedConfigs });
    } catch (error) {
      return Err(
        new Error(
          `Failed to write files: ${error instanceof Error ? error.message : String(error)}`
        )
      );
    }
  }
  /**
   * Check whether the target directory already contains a project by looking for
   * common build/package configuration files. Returns true if any marker is found.
   */
  isExistingProject(targetDir) {
    return PROJECT_MARKERS.some((marker) => fs.existsSync(path.join(targetDir, marker)));
  }
  detectFramework(targetDir) {
    try {
      const templatesResult = this.listTemplates();
      if (!templatesResult.ok) return Err(templatesResult.error);
      const candidates = [];
      for (const meta of templatesResult.value) {
        if (!meta.detect || meta.detect.length === 0) continue;
        if (!meta.framework || !meta.language) continue;
        const score = scoreDetectPatterns(targetDir, meta.detect);
        if (score > 0) {
          candidates.push({
            framework: meta.framework,
            language: meta.language,
            score,
            templateName: meta.name
          });
        }
      }
      candidates.sort((a, b) => b.score - a.score);
      return Ok(candidates);
    } catch (error) {
      return Err(
        new Error(
          `Framework detection failed: ${error instanceof Error ? error.message : String(error)}`
        )
      );
    }
  }
  resolveLanguageTemplate(language, framework) {
    const baseName = `${language}-base`;
    const baseDir = this.findTemplateDir(baseName, "name");
    if (!baseDir) return Err(new Error(`Language base template not found: ${baseName}`));
    const metaPath = path.join(baseDir, "template.json");
    const metaRaw = JSON.parse(fs.readFileSync(metaPath, "utf-8"));
    const metaResult = TemplateMetadataSchema.safeParse(metaRaw);
    if (!metaResult.success)
      return Err(new Error(`Invalid template.json in ${baseName}: ${metaResult.error.message}`));
    const metadata = metaResult.data;
    let files = this.collectFiles(baseDir, baseName);
    let overlayMetadata;
    if (framework) {
      const frameworkDir = this.findTemplateDir(framework, "framework");
      if (!frameworkDir) return Err(new Error(`Framework template not found: ${framework}`));
      const fMetaPath = path.join(frameworkDir, "template.json");
      const fMetaRaw = JSON.parse(fs.readFileSync(fMetaPath, "utf-8"));
      const fMetaResult = TemplateMetadataSchema.safeParse(fMetaRaw);
      if (fMetaResult.success) overlayMetadata = fMetaResult.data;
      const frameworkFiles = this.collectFiles(frameworkDir, framework);
      files = this.mergeFileLists(files, frameworkFiles);
    }
    files = files.filter((f) => f.relativePath !== "template.json");
    const resolved = { metadata, files };
    if (overlayMetadata !== void 0) resolved.overlayMetadata = overlayMetadata;
    return Ok(resolved);
  }
  findTemplateDir(name, type) {
    const entries = fs.readdirSync(this.templatesDir, { withFileTypes: true });
    for (const entry of entries) {
      if (!entry.isDirectory()) continue;
      const metaPath = path.join(this.templatesDir, entry.name, "template.json");
      if (!fs.existsSync(metaPath)) continue;
      const raw = JSON.parse(fs.readFileSync(metaPath, "utf-8"));
      const parsed = TemplateMetadataSchema.safeParse(raw);
      if (!parsed.success) continue;
      const fieldValue = parsed.data[type];
      if (fieldValue === name) return path.join(this.templatesDir, entry.name);
    }
    return null;
  }
  collectFiles(dir, sourceName) {
    const files = [];
    const walk = (currentDir) => {
      const entries = fs.readdirSync(currentDir, { withFileTypes: true });
      for (const entry of entries) {
        const fullPath = path.join(currentDir, entry.name);
        if (entry.isDirectory()) {
          walk(fullPath);
        } else {
          files.push({
            relativePath: path.relative(dir, fullPath).replace(/\\/g, "/"),
            absolutePath: fullPath,
            isHandlebars: entry.name.endsWith(".hbs"),
            sourceTemplate: sourceName
          });
        }
      }
    };
    walk(dir);
    return files;
  }
  mergeFileLists(base, overlay) {
    const map = /* @__PURE__ */ new Map();
    for (const file of base) map.set(file.relativePath, file);
    for (const file of overlay) {
      if (file.relativePath.endsWith(".json.hbs")) {
        const baseKey = base.find((f) => f.relativePath === file.relativePath);
        if (baseKey) {
          map.set(`__overlay__${file.relativePath}`, file);
        } else {
          map.set(file.relativePath, file);
        }
      } else {
        map.set(file.relativePath, file);
      }
    }
    return Array.from(map.values());
  }
};

export {
  TemplateEngine
};
