/**
 * Bundled content loader.
 *
 * The Worker bundle inlines `content.json` (uncondensed, schema_version 1)
 * at deploy time via `wrangler deploy`. R2 holds the gzipped archival copy
 * (Phase 3.4) but the Worker never fetches it at runtime — the bundle
 * IS the truth, per `mcp-cloud-scope.md` §3.3.
 *
 * The bundled JSON is generated by `scripts/pack_mcp_content.py` at
 * pipeline time. At Worker boot the content is validated once
 * (`assertManifest`) and frozen.
 */

import { assertManifest, type Manifest } from "./manifest.js";

export type PromptKind = "skill" | "command";
export type ResourceKind = "rule" | "guideline" | "context";
export type EntryKind = PromptKind | ResourceKind;

export type ContentEntry = {
  /** URI like `skill://verify-completion-evidence` or `rule://commit-policy`. */
  uri: string;
  /** Frontmatter `name:` value. */
  name: string;
  /** Frontmatter `description:` value or first-H1 fallback. */
  description: string;
  /** Body with frontmatter stripped. */
  body: string;
  /** `package` or `project` — from frontmatter `source:`. */
  source: string;
  kind: EntryKind;
  /** `text/markdown` for resources, omitted for prompts. */
  mime_type?: string;
};

export type ToolSideEffect = "ro" | "fs-write" | "shell";

export type ToolCatalogEntry = {
  name: string;
  description: string;
  side_effect: ToolSideEffect;
  /** Transports where the real handler is wired. Anything else → stub. */
  implemented_on: readonly string[];
  /** JSON Schema draft-7 — passed through verbatim to `tools/list`. */
  input_schema: Record<string, unknown>;
};

export type ToolCatalog = {
  schema_version: 1;
  /** Free-text — surfaced to humans only, not wire-meaningful. */
  description?: string;
  /** Stable shell command surfaced in the `not_implemented` envelope. */
  install_hint_stdio: string;
  tools: readonly ToolCatalogEntry[];
};

export type ContentBlob = {
  schema_version: 1;
  uris: Record<string, ContentEntry>;
  tool_catalog: ToolCatalog;
  manifest: Manifest;
};

/**
 * Validates a parsed JSON blob is a well-formed v1 ContentBlob. Throws on
 * mismatch — boot-time refusal is preferred to per-request failure.
 */
export function assertContentBlob(value: unknown): asserts value is ContentBlob {
  if (typeof value !== "object" || value === null) {
    throw new Error("content: not an object");
  }
  const b = value as Partial<ContentBlob>;
  if (b.schema_version !== 1) {
    throw new Error(
      `content: unsupported schema_version=${String(b.schema_version)}; expected 1`,
    );
  }
  if (typeof b.uris !== "object" || b.uris === null) {
    throw new Error("content: 'uris' must be an object");
  }
  if (typeof b.manifest !== "object" || b.manifest === null) {
    throw new Error("content: 'manifest' missing");
  }
  assertManifest(b.manifest);
  assertToolCatalog(b.tool_catalog);
}

function assertToolCatalog(value: unknown): asserts value is ToolCatalog {
  if (typeof value !== "object" || value === null) {
    throw new Error("content: 'tool_catalog' missing");
  }
  const c = value as Partial<ToolCatalog>;
  if (c.schema_version !== 1) {
    throw new Error(
      `tool_catalog: unsupported schema_version=${String(c.schema_version)}; expected 1`,
    );
  }
  if (typeof c.install_hint_stdio !== "string") {
    throw new Error("tool_catalog: 'install_hint_stdio' must be a string");
  }
  if (!Array.isArray(c.tools)) {
    throw new Error("tool_catalog: 'tools' must be an array");
  }
  for (const t of c.tools) {
    if (typeof t !== "object" || t === null) {
      throw new Error("tool_catalog: every tool must be an object");
    }
    const e = t as Partial<ToolCatalogEntry>;
    if (typeof e.name !== "string" || !e.name) {
      throw new Error("tool_catalog: tool 'name' must be a non-empty string");
    }
    if (typeof e.description !== "string") {
      throw new Error(`tool_catalog: tool ${e.name} missing description`);
    }
    if (e.side_effect !== "ro" && e.side_effect !== "fs-write" && e.side_effect !== "shell") {
      throw new Error(`tool_catalog: tool ${e.name} has invalid side_effect`);
    }
    if (!Array.isArray(e.implemented_on)) {
      throw new Error(`tool_catalog: tool ${e.name} missing implemented_on array`);
    }
    if (typeof e.input_schema !== "object" || e.input_schema === null) {
      throw new Error(`tool_catalog: tool ${e.name} missing input_schema`);
    }
  }
}

/**
 * Filters entries by URI scheme. Used by `prompts/list` (skill, command)
 * and `resources/list` (rule, guideline, context).
 */
export function entriesOfKind(blob: ContentBlob, kinds: readonly EntryKind[]): ContentEntry[] {
  const set = new Set<EntryKind>(kinds);
  return Object.values(blob.uris).filter((e) => set.has(e.kind));
}

/**
 * Stub blob used by dev / tests when no packed content is present. The
 * production bundle replaces this via the build step (Phase 3.3).
 * Signature `0`*12 makes it obvious in logs that this is a placeholder.
 */
export const STUB_BLOB: ContentBlob = {
  schema_version: 1,
  uris: {},
  tool_catalog: {
    schema_version: 1,
    install_hint_stdio: "pip install agent-config[mcp] && ./agent-config mcp:run",
    tools: [],
  },
  manifest: {
    schema_version: 1,
    signature: "000000000000",
    content_hash_sha256:
      "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
    package_version: "0.0.0-dev",
    release_key: "v0.0.0-dev",
    git_sha: "0000000000000000000000000000000000000000",
    built_at: "1970-01-01T00:00:00Z",
    packer_version: "0.0.0",
    content_uri_count: { skill: 0, command: 0, rule: 0, guideline: 0, context: 0 },
    tool_count: 0,
  },
};
