import { readFileSync, mkdirSync, writeFileSync, readdirSync } from 'fs';
import { relative, dirname, resolve, join, extname } from 'path';
import { compile } from '@mdx-js/mdx';
import remarkGfm from 'remark-gfm';

// src/knowledge-codegen.ts
function targetFromNodeId(nodeId) {
  const [kind, ...idParts] = nodeId.split(":");
  return {
    kind,
    id: idParts.join(":")
  };
}
function canonicalizeKnowledgeNodeLinks(node) {
  return {
    ...node,
    links: node.links.map((link) => ({
      target: targetFromNodeId(link.nodeId),
      nodeId: link.nodeId
    }))
  };
}
function listMdxFiles(directory) {
  return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
    const path = join(directory, entry.name);
    if (entry.isDirectory()) return listMdxFiles(path);
    return entry.isFile() && extname(entry.name) === ".mdx" ? [path] : [];
  });
}
function parseScalar(value) {
  const trimmed = value.trim();
  const quoted = trimmed.match(/^['"]([\s\S]*)['"]$/);
  return quoted ? quoted[1] : trimmed;
}
function parseFrontmatter(raw, filePath) {
  const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
  if (!match) throw new Error(`[knowledge-node-codegen] Missing frontmatter in ${filePath}`);
  const frontmatter = {};
  const lines = match[1].split(/\r?\n/);
  for (let index = 0; index < lines.length; index += 1) {
    const line = lines[index];
    if (!line.trim()) continue;
    const pair = line.match(/^([A-Za-z][A-Za-z0-9]*):(?:\s*(.*))?$/);
    if (!pair) throw new Error(`[knowledge-node-codegen] Invalid frontmatter line in ${filePath}: ${line}`);
    const key = pair[1];
    const inlineValue = pair[2]?.trim() ?? "";
    if (inlineValue) {
      frontmatter[key] = parseScalar(inlineValue);
      continue;
    }
    const values = [];
    while (index + 1 < lines.length && /^\s+-\s+/.test(lines[index + 1])) {
      index += 1;
      values.push(parseScalar(lines[index].replace(/^\s+-\s+/, "")));
    }
    frontmatter[key] = values;
  }
  return { frontmatter, body: match[2].trim() };
}
function assertString(frontmatter, key, filePath) {
  const value = frontmatter[key];
  if (typeof value !== "string" || value.trim().length === 0) {
    throw new Error(`[knowledge-node-codegen] ${filePath} frontmatter requires string "${key}"`);
  }
  return value.trim();
}
function optionalString(frontmatter, key, filePath) {
  const value = frontmatter[key];
  if (value === void 0) return void 0;
  if (typeof value !== "string" || value.trim().length === 0) {
    throw new Error(`[knowledge-node-codegen] ${filePath} frontmatter "${key}" must be a nonempty string`);
  }
  return value.trim();
}
function assertKind(value, filePath) {
  if (value === "playbook" || value === "strategy" || value === "reference") return value;
  throw new Error(`[knowledge-node-codegen] ${filePath} has invalid kind "${value}"`);
}
function optionalStringArray(frontmatter, key, filePath) {
  const value = frontmatter[key];
  if (value === void 0) return [];
  if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
    throw new Error(`[knowledge-node-codegen] ${filePath} frontmatter "${key}" must be a string array`);
  }
  return value.map((entry) => entry.trim()).filter(Boolean);
}
var ROUTING_STOPWORDS = /* @__PURE__ */ new Set([
  "about",
  "after",
  "again",
  "against",
  "all",
  "also",
  "and",
  "any",
  "are",
  "before",
  "but",
  "can",
  "cannot",
  "could",
  "for",
  "from",
  "has",
  "have",
  "how",
  "into",
  "its",
  "may",
  "more",
  "must",
  "not",
  "now",
  "off",
  "one",
  "only",
  "our",
  "out",
  "over",
  "per",
  "run",
  "same",
  "should",
  "than",
  "that",
  "the",
  "their",
  "then",
  "there",
  "this",
  "through",
  "use",
  "uses",
  "using",
  "when",
  "where",
  "with",
  "workflow",
  "workflows",
  "would",
  "you",
  "your"
]);
function toRegistryPath(path) {
  return path.replace(/\\/g, "/");
}
function sourceBaseFromLabel(sourceLabel) {
  if (!sourceLabel) return void 0;
  const normalized = toRegistryPath(sourceLabel).replace(/\/+$/, "");
  const globIndex = normalized.indexOf("*");
  if (globIndex === -1) return normalized;
  return normalized.slice(0, globIndex).replace(/\/+$/, "");
}
function toSourceFilePath(sourceLabel, relativePath) {
  const normalizedRelativePath = toRegistryPath(relativePath);
  const sourceBase = sourceBaseFromLabel(sourceLabel);
  return sourceBase ? `${sourceBase}/${normalizedRelativePath}` : normalizedRelativePath;
}
function slugify(value) {
  return value.trim().toLowerCase().replace(/['"]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
}
function toTitle(value) {
  return value.split(/[-:\s]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
}
function tokenizeRoutingText(...values) {
  return values.join(" ").toLowerCase().replace(/```[\s\S]*?```/g, " ").replace(/[\\/]/g, " ").match(/[a-z0-9]+(?:[.-][a-z0-9]+)*/g)?.flatMap((token) => {
    const expanded = token.includes(".") ? [token, ...token.split(".")] : [token];
    return expanded.flatMap((entry) => entry.includes("-") ? [entry, ...entry.split("-")] : [entry]);
  }) ?? [];
}
function rankedTerms(values, limit) {
  const counts = /* @__PURE__ */ new Map();
  for (const value of values) {
    const token = slugify(value);
    if (!token || ROUTING_STOPWORDS.has(token)) continue;
    if (token.length < 3 && token !== "ai") continue;
    counts.set(token, (counts.get(token) ?? 0) + 1);
  }
  return [...counts.entries()].sort(([termA, countA], [termB, countB]) => countB - countA || termA.localeCompare(termB)).slice(0, limit).map(([term]) => term);
}
function uniqueSorted(values) {
  return [...new Set(values.filter(Boolean))].sort((a, b) => a.localeCompare(b));
}
function nodeRoutingTerms(node, sourcePath, limit) {
  return rankedTerms(
    tokenizeRoutingText(
      node.id,
      node.kind,
      node.title,
      node.summary,
      node.body,
      sourcePath,
      ...node.links.map((link) => link.nodeId)
    ),
    limit
  );
}
function nodeTags(node, sourcePath) {
  return uniqueSorted([
    node.kind,
    ...tokenizeRoutingText(
      node.title,
      node.summary,
      sourcePath,
      ...node.links.map((link) => link.nodeId)
    ).map(slugify).filter((term) => term && !ROUTING_STOPWORDS.has(term) && (term.length >= 3 || term === "ai")).slice(0, 20)
  ]);
}
function readCommandForNode(node, cliCommand = "elevasis") {
  return [
    {
      command: `pnpm exec ${cliCommand} knowledge:cat ${node.id}`,
      description: `Read ${node.title}`
    },
    {
      command: `pnpm exec ${cliCommand} knowledge:ls /by-kind/${node.kind}`,
      description: `List ${node.kind} knowledge nodes`
    }
  ];
}
function readKnowledgeNodeMdx(filePath) {
  const raw = readFileSync(filePath, "utf8");
  const { frontmatter, body } = parseFrontmatter(raw, filePath);
  return {
    id: assertString(frontmatter, "id", filePath),
    kind: assertKind(assertString(frontmatter, "kind", filePath), filePath),
    title: assertString(frontmatter, "title", filePath),
    summary: assertString(frontmatter, "description", filePath),
    icon: optionalString(frontmatter, "icon", filePath),
    body,
    links: optionalStringArray(frontmatter, "links", filePath).map((nodeId) => ({ nodeId })),
    ownerIds: optionalStringArray(frontmatter, "ownerIds", filePath),
    updatedAt: assertString(frontmatter, "updatedAt", filePath)
  };
}
function generateKnowledgeFlagRegistry(nodes, sourcePaths = {}, options = {}) {
  const routes = /* @__PURE__ */ new Map();
  for (const node of nodes) {
    const routeKey = `kind-${node.kind}`;
    const flag = `--${slugify(routeKey)}`;
    const sourcePath = toRegistryPath(sourcePaths[node.id] ?? "");
    const routingTerms = nodeRoutingTerms(node, sourcePath, 24);
    const tags = nodeTags(node, sourcePath);
    const cliBindings = readCommandForNode(node, options.cliCommand);
    const route = routes.get(flag) ?? {
      flag,
      label: toTitle(routeKey),
      kind: node.kind,
      tags: [],
      queryTerms: [],
      cliBindings: [],
      nodes: []
    };
    route.tags = uniqueSorted([...route.tags, ...tags]);
    route.queryTerms = rankedTerms([...route.queryTerms, ...routingTerms], 40);
    route.cliBindings = [...route.cliBindings, ...cliBindings].filter(
      (binding, index, bindings) => bindings.findIndex((candidate) => candidate.command === binding.command) === index
    );
    route.nodes.push({
      id: node.id,
      title: node.title,
      kind: node.kind,
      sourcePath,
      tags,
      routingTerms,
      links: node.links.map((link) => link.nodeId),
      cliBindings
    });
    routes.set(flag, route);
  }
  const sortedRoutes = [...routes.values()].sort((a, b) => a.flag.localeCompare(b.flag));
  const aliasCandidates = sortedRoutes.flatMap((route) => {
    const aliasTerms = uniqueSorted([
      route.flag.replace(/^--/, ""),
      route.label.toLowerCase(),
      ...route.tags.slice(0, 12),
      ...route.queryTerms.slice(0, 12)
    ]);
    return aliasTerms.map((alias) => [alias, route.flag]);
  });
  const aliasCounts = aliasCandidates.reduce((counts, [alias]) => {
    counts.set(alias, (counts.get(alias) ?? 0) + 1);
    return counts;
  }, /* @__PURE__ */ new Map());
  const aliases = Object.fromEntries(
    aliasCandidates.filter(([alias]) => aliasCounts.get(alias) === 1).sort(([aliasA], [aliasB]) => aliasA.localeCompare(aliasB))
  );
  return {
    generatedBy: "generate-knowledge-nodes",
    routingMode: "deterministic-keyword",
    flags: Object.fromEntries(
      sortedRoutes.map((route) => [
        route.flag,
        {
          ...route,
          cliBindings: route.cliBindings.sort((a, b) => a.command.localeCompare(b.command)),
          nodes: route.nodes.sort((a, b) => a.id.localeCompare(b.id))
        }
      ])
    ),
    aliases
  };
}
function generateKnowledgeNodesTs(options) {
  const exportedName = options.exportedName ?? "mdxKnowledgeNodes";
  const typeImport = options.typeImportPath ? [`import type { OrgKnowledgeNode } from '${options.typeImportPath}'`, ""] : [];
  const typeSatisfies = options.typeImportPath ? " satisfies OrgKnowledgeNode[]" : "";
  return [
    "// @generated by generate-knowledge-nodes -- DO NOT EDIT",
    "// Regenerate: elevasis-sdk knowledge:generate",
    `// Source: ${options.sourceLabel ?? "knowledge/nodes/**/*.mdx"}`,
    "",
    ...typeImport,
    `export const ${exportedName} = ${JSON.stringify(options.nodes.map(canonicalizeKnowledgeNodeLinks), null, 2)}${typeSatisfies}`,
    ""
  ].join("\n");
}
function generateKnowledgeNodes(options) {
  const files = listMdxFiles(options.sourceDir).sort(
    (a, b) => relative(options.sourceDir, a).localeCompare(relative(options.sourceDir, b))
  );
  const nodes = files.map((file) => {
    const relativePath = relative(options.sourceDir, file);
    return {
      ...readKnowledgeNodeMdx(file),
      sourceFilePath: toSourceFilePath(options.sourceLabel, relativePath)
    };
  });
  const sourcePaths = Object.fromEntries(
    files.map((file, index) => [nodes[index].id, toRegistryPath(relative(options.sourceDir, file))])
  );
  const ids = /* @__PURE__ */ new Set();
  for (const node of nodes) {
    if (ids.has(node.id)) throw new Error(`[knowledge-node-codegen] Duplicate knowledge node id: ${node.id}`);
    ids.add(node.id);
  }
  mkdirSync(dirname(options.outputPath), { recursive: true });
  writeFileSync(
    options.outputPath,
    generateKnowledgeNodesTs({
      nodes,
      typeImportPath: options.typeImportPath,
      exportedName: options.exportedName,
      sourceLabel: options.sourceLabel
    }),
    "utf8"
  );
  if (options.knowledgeFlagsOutputPath) {
    mkdirSync(dirname(options.knowledgeFlagsOutputPath), { recursive: true });
    writeFileSync(
      options.knowledgeFlagsOutputPath,
      `${JSON.stringify(
        generateKnowledgeFlagRegistry(nodes, sourcePaths, {
          cliCommand: options.knowledgeFlagsCliCommand ?? "elevasis"
        }),
        null,
        2
      )}
`,
      "utf8"
    );
  }
  return { nodes, outputPath: options.outputPath };
}
var ALLOWED_COMPONENTS = /* @__PURE__ */ new Set(["Card", "Cards", "Step", "Steps", "Callout", "Tab", "Tabs"]);
function extractCustomComponents(compiledCode) {
  const found = /* @__PURE__ */ new Set();
  const destructurePattern = /\{([^}]+)\}\s*=\s*_components/g;
  let match;
  while ((match = destructurePattern.exec(compiledCode)) !== null) {
    for (const part of match[1].split(",")) {
      const name = part.trim().split(":")[0].trim();
      if (name && /^[A-Z]/.test(name)) {
        found.add(name);
      }
    }
  }
  return [...found];
}
function validateComponents(nodeId, compiledCode) {
  const components = extractCustomComponents(compiledCode);
  const unknown = components.filter((c) => !ALLOWED_COMPONENTS.has(c));
  if (unknown.length > 0) {
    throw new Error(
      `[knowledge-codegen] node "${nodeId}" uses unknown JSX component(s): ${unknown.join(", ")}.
Allowed components: ${[...ALLOWED_COMPONENTS].join(", ")}.
To add a component:
  1. Add it to ALLOWED_COMPONENTS in packages/sdk/src/node/knowledge-bodies-codegen.ts
  2. Add it to the runtime KnowledgeMDXProvider allowlist (Wave 3d)`
    );
  }
}
function stripToPlainText(body) {
  return body.replace(/^(import|export)\s+.+$/gm, "").replace(/<[A-Z][^>]*>/g, "").replace(/<\/[A-Z][^>]*>/g, "").replace(/^#{1,6}\s+/gm, "").replace(/[*_`~]/g, "").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/\n{3,}/g, "\n\n").trim();
}
var BODIES_HEADER = [
  "// @generated by generate-knowledge-bodies -- DO NOT EDIT",
  "// Regenerate: pnpm scaffold:sync",
  "// Source: packages/elevasis-core/src/organization-model/index.ts",
  "",
  "import { Fragment, jsx, jsxs } from 'react/jsx-runtime'",
  "import type { ComponentType } from 'react'",
  ""
].join("\n");
var FACTORY_BLOCK = [
  "// ---------------------------------------------------------------------------",
  "// Inline compiled MDX component factory",
  "// ---------------------------------------------------------------------------",
  "",
  "type MDXRuntime = {",
  "  Fragment: typeof Fragment",
  "  jsx: typeof jsx",
  "  jsxs: typeof jsxs",
  "}",
  "",
  "type KnowledgeBodyProps = {",
  "  components?: Record<string, ComponentType>",
  "}",
  "",
  "/**",
  " * Wraps a compiled MDX function-body string into a React component.",
  " * The function-body format from @mdx-js/mdx expects arguments[0]",
  " * to be { Fragment, jsx, jsxs } from react/jsx-runtime.",
  " */",
  "function makeKnowledgeComponent(fnBody: string): ComponentType<KnowledgeBodyProps> {",
  "  const factory = new Function(fnBody) as (runtime: MDXRuntime) => { default: ComponentType }",
  "  return function KnowledgeBody(props: KnowledgeBodyProps) {",
  "    const runtime = { Fragment, jsx, jsxs, ...props } as unknown as MDXRuntime",
  "    const mod = factory(runtime)",
  "    const Content = mod.default",
  "    return Content ? jsx(Content as ComponentType<KnowledgeBodyProps>, props) : null",
  "  }",
  "}",
  ""
].join("\n");
var MAP_HEADER = [
  "// ---------------------------------------------------------------------------",
  "// Generated map: node id -> compiled React component",
  "// ---------------------------------------------------------------------------",
  ""
].join("\n");
async function generateKnowledgeBodies(nodes) {
  if (nodes.length === 0) {
    const bodiesTsx2 = [
      "// @generated by generate-knowledge-bodies -- DO NOT EDIT",
      "// Regenerate: pnpm scaffold:sync",
      "// Source: packages/elevasis-core/src/organization-model/index.ts",
      "",
      "import type { ComponentType } from 'react'",
      "",
      "export const KNOWLEDGE_BODIES: Record<",
      "  string,",
      "  ComponentType<{ components?: Record<string, ComponentType> }>",
      "> = {}",
      ""
    ].join("\n");
    return { bodiesTsx: bodiesTsx2, searchIndex: [] };
  }
  const nodeComments = [];
  const mapEntries = [];
  const searchIndex = [];
  for (const node of nodes) {
    if (/^(import|export)\s+/m.test(node.body)) {
      throw new Error(
        `[knowledge-codegen] node "${node.id}" body contains import/export statements, which are not allowed in Phase 1 MDX bodies.`
      );
    }
    const compiled = await compile(node.body, {
      outputFormat: "function-body",
      jsx: false,
      remarkPlugins: [remarkGfm]
    });
    const fnBodyCode = String(compiled);
    validateComponents(node.id, fnBodyCode);
    const bodyText = stripToPlainText(node.body);
    searchIndex.push({ id: node.id, title: node.title, summary: node.summary, bodyText });
    nodeComments.push(`// Node: ${node.id} (${node.kind})`);
    mapEntries.push(`  '${node.id}': makeKnowledgeComponent(${JSON.stringify(fnBodyCode)})`);
  }
  const mapBlock = `export const KNOWLEDGE_BODIES: Record<string, ComponentType<KnowledgeBodyProps>> = {
` + mapEntries.join(",\n") + "\n}\n";
  const bodiesTsx = BODIES_HEADER + FACTORY_BLOCK + MAP_HEADER + (nodeComments.length > 0 ? nodeComments.join("\n") + "\n\n" : "") + mapBlock;
  return { bodiesTsx, searchIndex };
}
async function runKnowledgeCodegen(layout) {
  let nodes;
  if (layout.mode === "monorepo") {
    const omSpecifier = "@repo/elevasis-core/organization-model";
    const om = await import(
      /* @vite-ignore */
      omSpecifier
    );
    nodes = om.canonicalOrganizationModel.knowledge.nodes;
  } else {
    const outputPath = resolve(layout.generatedDir, "nodes.ts");
    const result = generateKnowledgeNodes({
      sourceDir: layout.mdxNodesDir,
      outputPath,
      typeImportPath: "@elevasis/core/organization-model",
      exportedName: "mdxKnowledgeNodes"
    });
    nodes = result.nodes;
  }
  const { bodiesTsx, searchIndex } = await generateKnowledgeBodies(nodes);
  mkdirSync(layout.generatedDir, { recursive: true });
  writeFileSync(resolve(layout.generatedDir, "knowledge-bodies.tsx"), bodiesTsx, "utf8");
  writeFileSync(
    resolve(layout.generatedDir, "knowledge-search-index.json"),
    JSON.stringify(searchIndex, null, 2) + "\n",
    "utf8"
  );
}

// src/project-deployment-spec.ts
function toSdkResourceDescriptor(resource, getResourceOntologyBinding) {
  const ontologyBinding = getResourceOntologyBinding?.(resource.id);
  return {
    ...resource,
    ...ontologyBinding ?? {},
    systemId: resource.systemPath
  };
}
function withPlatformResourceDescriptor(workflow, getWorkflowResourceDescriptor, getResourceOntologyBinding) {
  const resource = getWorkflowResourceDescriptor(workflow.config.resourceId);
  const sdkResource = toSdkResourceDescriptor(resource, getResourceOntologyBinding);
  return {
    ...workflow,
    config: {
      ...workflow.config,
      resource: sdkResource,
      resourceId: sdkResource.id,
      type: sdkResource.kind
    }
  };
}
function withPlatformResourceDescriptors(workflows, getWorkflowResourceDescriptor, getResourceOntologyBinding) {
  return workflows.map(
    (workflow) => withPlatformResourceDescriptor(workflow, getWorkflowResourceDescriptor, getResourceOntologyBinding)
  );
}
function withPlatformIntegrationResourceDescriptor(integration, getIntegrationResourceDescriptor, getResourceOntologyBinding) {
  const resource = getIntegrationResourceDescriptor(integration.resourceId);
  const sdkResource = toSdkResourceDescriptor(resource, getResourceOntologyBinding);
  return {
    ...integration,
    resource: sdkResource,
    resourceId: sdkResource.id,
    type: sdkResource.kind
  };
}
function withPlatformIntegrationResourceDescriptors(integrations, getIntegrationResourceDescriptor, getResourceOntologyBinding) {
  return integrations.map(
    (integration) => withPlatformIntegrationResourceDescriptor(integration, getIntegrationResourceDescriptor, getResourceOntologyBinding)
  );
}
function projectTopologyRelationships(model) {
  const projected = {};
  function ensure(sourceId) {
    projected[sourceId] ??= {};
    return projected[sourceId];
  }
  for (const relationship of Object.values(model.topology.relationships)) {
    if (relationship.from.kind === "humanCheckpoint") continue;
    if (relationship.kind === "triggers" && relationship.to.kind === "resource") {
      const target = model.resources[relationship.to.id];
      if (target?.kind !== "workflow" && target?.kind !== "agent") continue;
      const source = ensure(relationship.from.id);
      source.triggers ??= {};
      if (target.kind === "workflow") {
        source.triggers.workflows ??= [];
        source.triggers.workflows.push(target.id);
      } else {
        source.triggers.agents ??= [];
        source.triggers.agents.push(target.id);
      }
      continue;
    }
    if (relationship.kind === "uses" && relationship.from.kind === "resource" && relationship.to.kind === "resource") {
      const target = model.resources[relationship.to.id];
      if (target?.kind !== "integration") continue;
      const source = ensure(relationship.from.id);
      source.uses ??= {};
      source.uses.integrations ??= [];
      source.uses.integrations.push(target.id);
    }
  }
  return projected;
}
function projectDeploymentSpec(options) {
  const workflows = withPlatformResourceDescriptors(
    options.workflows,
    options.getWorkflowResourceDescriptor,
    options.getResourceOntologyBinding
  );
  const integrations = withPlatformIntegrationResourceDescriptors(
    options.integrations ?? [],
    options.getIntegrationResourceDescriptor,
    options.getResourceOntologyBinding
  );
  return {
    version: options.version,
    organizationModel: options.organizationModel,
    workflows,
    agents: options.agents ?? [],
    triggers: options.triggers,
    integrations,
    humanCheckpoints: options.humanCheckpoints,
    relationships: projectTopologyRelationships(options.organizationModel),
    ...options.externalResources ? { externalResources: options.externalResources } : {}
  };
}

export { generateKnowledgeBodies, generateKnowledgeNodes, generateKnowledgeNodesTs, projectDeploymentSpec, projectTopologyRelationships, readKnowledgeNodeMdx, runKnowledgeCodegen, toSdkResourceDescriptor, withPlatformIntegrationResourceDescriptor, withPlatformIntegrationResourceDescriptors, withPlatformResourceDescriptor, withPlatformResourceDescriptors };
