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

const fs = require("fs");
const path = require("path");

const repoRoot = path.resolve(__dirname, "..");
const stdDir = path.join(repoRoot, "lib", "std");
const outputPath = path.join(repoRoot, "docs", "language", "STDLIB_API.md");
const readmePath = path.join(repoRoot, "README.md");

const TIERS = Object.freeze({
  BETA_SUPPORTED: "beta-supported",
  EXPERIMENTAL: "experimental",
  INTERNAL: "internal",
});

const EXPERIMENTAL_MODULES = new Set(["json", "net", "random", "time"]);

const EXPERIMENTAL_HELPERS = new Map([
  [
    "fs",
    new Set([
      "open_text_read_result",
      "read_open_text_result",
      "close_result",
      "read_text_via_handle_result",
      "close_ok",
    ]),
  ],
]);

const CONCRETE_VEC_MODULES = new Set(["vec_bool", "vec_f64", "vec_i32", "vec_i64", "vec_string"]);

function helperTier(moduleName, exportName) {
  if (EXPERIMENTAL_MODULES.has(moduleName)) {
    return TIERS.EXPERIMENTAL;
  }

  const experimentalHelpers = EXPERIMENTAL_HELPERS.get(moduleName);
  if (experimentalHelpers && experimentalHelpers.has(exportName)) {
    return TIERS.EXPERIMENTAL;
  }

  return TIERS.BETA_SUPPORTED;
}

function uniqueSorted(values) {
  return Array.from(new Set(values)).sort();
}

function tokenize(source) {
  const tokens = [];
  let index = 0;

  while (index < source.length) {
    const ch = source[index];

    if (/\s/.test(ch)) {
      index += 1;
      continue;
    }

    if (ch === ";") {
      while (index < source.length && source[index] !== "\n") {
        index += 1;
      }
      continue;
    }

    if (ch === "(" || ch === ")") {
      tokens.push(ch);
      index += 1;
      continue;
    }

    if (ch === "\"") {
      const start = index;
      index += 1;
      while (index < source.length) {
        if (source[index] === "\\") {
          index += 2;
          continue;
        }
        if (source[index] === "\"") {
          index += 1;
          break;
        }
        index += 1;
      }
      if (source[index - 1] !== "\"") {
        throw new Error("unterminated string literal");
      }
      tokens.push(source.slice(start, index));
      continue;
    }

    const start = index;
    while (
      index < source.length &&
      !/\s/.test(source[index]) &&
      source[index] !== "(" &&
      source[index] !== ")" &&
      source[index] !== ";"
    ) {
      index += 1;
    }
    tokens.push(source.slice(start, index));
  }

  return tokens;
}

function parseList(tokens, cursor) {
  if (tokens[cursor.index] !== "(") {
    throw new Error(`expected list at token ${cursor.index}`);
  }
  cursor.index += 1;
  const list = [];
  while (cursor.index < tokens.length && tokens[cursor.index] !== ")") {
    if (tokens[cursor.index] === "(") {
      list.push(parseList(tokens, cursor));
    } else {
      list.push(tokens[cursor.index]);
      cursor.index += 1;
    }
  }
  if (tokens[cursor.index] !== ")") {
    throw new Error("unterminated list");
  }
  cursor.index += 1;
  return list;
}

function topLevelForms(source, file) {
  const tokens = tokenize(source);
  const cursor = { index: 0 };
  const forms = [];
  while (cursor.index < tokens.length) {
    if (tokens[cursor.index] !== "(") {
      throw new Error(`${file}: expected top-level list at token ${cursor.index}`);
    }
    forms.push(parseList(tokens, cursor));
  }
  return forms;
}

function moduleForm(forms, file) {
  const form = forms.find((item) => Array.isArray(item) && item[0] === "module");
  if (!form) {
    throw new Error(`${file}: expected module form`);
  }
  if (form[0] !== "module" || typeof form[1] !== "string") {
    throw new Error(`${file}: expected (module name ...) form`);
  }
  const exportForm = form.find((item) => Array.isArray(item) && item[0] === "export");
  if (!exportForm) {
    throw new Error(`${file}: module must have an explicit export list`);
  }
  return {
    name: form[1],
    exports: exportForm.slice(1),
  };
}

function renderType(type, aliases, stack = []) {
  if (Array.isArray(type)) {
    return `(${type.map((part) => renderType(part, aliases, stack)).join(" ")})`;
  }

  if (aliases.has(type)) {
    if (stack.includes(type)) {
      throw new Error(`cyclic type alias while rendering ${stack.concat(type).join(" -> ")}`);
    }
    return renderType(aliases.get(type), aliases, stack.concat(type));
  }

  return type;
}

function functionSignature(fnForm, aliases, file) {
  if (fnForm.length < 5 || typeof fnForm[1] !== "string" || !Array.isArray(fnForm[2]) || fnForm[3] !== "->") {
    throw new Error(`${file}: malformed (fn ...) form for ${fnForm[1] || "<unknown>"}`);
  }

  const name = fnForm[1];
  const params = fnForm[2].map((param) => {
    if (!Array.isArray(param) || param.length !== 2 || typeof param[0] !== "string") {
      throw new Error(`${file}: malformed parameter in ${name}`);
    }
    return `(${param[0]} ${renderType(param[1], aliases)})`;
  });
  const renderedParams = params.length === 0 ? "()" : `(${params.join(" ")})`;
  const returnType = renderType(fnForm[4], aliases);
  return `${name} ${renderedParams} -> ${returnType}`;
}

function indexForms(forms, file) {
  const aliases = new Map();
  const functions = new Map();

  for (const form of forms) {
    if (!Array.isArray(form) || form.length < 1) {
      continue;
    }

    if (form[0] === "type") {
      if (form.length !== 3 || typeof form[1] !== "string") {
        throw new Error(`${file}: malformed (type ...) form`);
      }
      if (aliases.has(form[1]) || functions.has(form[1])) {
        throw new Error(`${file}: duplicate public name ${form[1]}`);
      }
      aliases.set(form[1], form[2]);
    }

    if (form[0] === "fn") {
      if (typeof form[1] !== "string") {
        throw new Error(`${file}: malformed (fn ...) form`);
      }
      if (functions.has(form[1]) || aliases.has(form[1])) {
        throw new Error(`${file}: duplicate public name ${form[1]}`);
      }
      functions.set(form[1], form);
    }
  }

  return { aliases, functions };
}

function stdModules() {
  return fs
    .readdirSync(stdDir)
    .filter((name) => name.endsWith(".slo"))
    .sort()
    .map((fileName) => {
      const relativePath = path.join("lib", "std", fileName);
      const source = fs.readFileSync(path.join(stdDir, fileName), "utf8");
      const forms = topLevelForms(source, relativePath);
      const module = moduleForm(forms, relativePath);
      const { aliases, functions } = indexForms(forms, relativePath);
      const omittedAliasExports = [];
      const signatures = [];

      for (const exported of module.exports) {
        if (functions.has(exported)) {
          signatures.push({
            name: exported,
            signature: functionSignature(functions.get(exported), aliases, relativePath),
            tier: helperTier(module.name, exported),
          });
        } else if (aliases.has(exported)) {
          omittedAliasExports.push(exported);
        } else {
          throw new Error(`${relativePath}: export ${exported} has no matching (fn ...) form`);
        }
      }

      return {
        fileName,
        relativePath,
        name: module.name,
        tiers: uniqueSorted(signatures.map((signature) => signature.tier)),
        signatures,
        omittedAliasExports,
      };
    });
}

function releaseVersion() {
  const readme = fs.readFileSync(readmePath, "utf8");
  const versionMatch = readme.match(/^Current release:\s*`([^`]+)`\.$/m);
  if (!versionMatch) {
    throw new Error("README.md must declare the current release");
  }
  return versionMatch[1];
}

function render(modules, version) {
  const totalExports = modules.reduce((count, module) => count + module.signatures.length, 0);
  const totalOmittedAliases = modules.reduce((count, module) => count + module.omittedAliasExports.length, 0);
  const tierCounts = new Map([
    [TIERS.BETA_SUPPORTED, 0],
    [TIERS.EXPERIMENTAL, 0],
  ]);
  for (const module of modules) {
    for (const signature of module.signatures) {
      tierCounts.set(signature.tier, (tierCounts.get(signature.tier) || 0) + 1);
    }
  }
  const out = [];
  out.push("# Slovo Standard Library API Catalog");
  out.push("");
  out.push("Generated from `lib/std/*.slo` by `scripts/render-stdlib-api-doc.js`.");
  out.push("Do not edit this file by hand.");
  out.push("");
  out.push("## Stability Tiers");
  out.push("");
  out.push("- `beta-supported`: exported from `lib/std` and covered by source-search, promotion, or facade gates in the current beta line.");
  out.push(`- \`experimental\`: exported from \`lib/std\` in \`${version}\`, but still has beta caveats around host behavior, resource handles, or API shape.`);
  out.push("- `internal`: helper names that are not exported from their module; they are intentionally omitted from this catalog.");
  out.push("");
  out.push("The catalog is a beta API discovery aid, not a stable `1.0.0` standard-library freeze.");
  out.push("Module-local concrete aliases are normalized in signatures so names such as `VecI32` and `ResultU64` do not leak into the public catalog.");
  out.push("Only exported `(fn ...)` helpers are listed; `(type ...)` aliases and non-exported helpers are omitted.");
  out.push("Concrete `std.vec_*` modules are beta-supported as concrete helper families only; this does not freeze a generic collection API.");
  out.push("");
  out.push("## Summary");
  out.push("");
  out.push(`- Modules: ${modules.length}`);
  out.push(`- Exported helper signatures: ${totalExports}`);
  out.push(`- Exported type aliases omitted: ${totalOmittedAliases}`);
  out.push("- Default tier: `beta-supported`");
  out.push(`- \`${TIERS.BETA_SUPPORTED}\` helper signatures: ${tierCounts.get(TIERS.BETA_SUPPORTED) || 0}`);
  out.push(`- \`${TIERS.EXPERIMENTAL}\` helper signatures: ${tierCounts.get(TIERS.EXPERIMENTAL) || 0}`);
  out.push("");
  out.push("## Modules");
  out.push("");
  for (const module of modules) {
    out.push(`### std.${module.name}`);
    out.push("");
    out.push(`- Path: \`${module.relativePath}\``);
    out.push(`- Tiers: ${module.tiers.map((tier) => `\`${tier}\``).join(", ")}`);
    out.push(`- Exported helper signatures: ${module.signatures.length}`);
    if (CONCRETE_VEC_MODULES.has(module.name)) {
      out.push("- Note: concrete-only vector helper family; no generic collection freeze.");
    }
    out.push("");
    for (const { signature, tier } of module.signatures) {
      out.push(`- \`${tier}\` \`${signature}\``);
    }
    out.push("");
  }
  return `${out.join("\n")}\n`;
}

fs.writeFileSync(outputPath, render(stdModules(), releaseVersion()));
