import path from "node:path";
import { parseDocument as parseConfigDocument, resolve as resolveConfigExtends } from "@poe-code/config-extends";
import { FrontmatterParseError, parseFrontmatterDocument } from "@poe-code/frontmatter";
export const superintendentDocumentSchemaId = "https://poe-platform.github.io/poe-code/schemas/plans/superintendent.schema.json";
export const superintendentBaseDocumentSchemaId = "https://poe-platform.github.io/poe-code/schemas/plans/superintendent-base.schema.json";
const mcpConfigSchema = {
    type: "object",
    properties: {
        command: {
            type: "string",
            minLength: 1
        },
        args: {
            type: "array",
            items: {
                type: "string"
            }
        },
        timeout: {
            type: "number",
            exclusiveMinimum: 0
        }
    },
    required: ["command"],
    additionalProperties: false
};
const agentRoleSchema = {
    type: "object",
    properties: {
        agent: {
            type: "string",
            minLength: 1,
            default: "claude-code"
        },
        mode: {
            type: "string",
            minLength: 1
        },
        cwd: {
            type: "string",
            minLength: 1
        },
        mcp: {
            type: "object",
            additionalProperties: mcpConfigSchema
        },
        prompt: {
            type: "string",
            minLength: 1
        }
    },
    required: ["prompt"],
    additionalProperties: false
};
const partialAgentRoleSchema = {
    type: "object",
    properties: {
        agent: {
            type: "string",
            minLength: 1,
            default: "claude-code"
        },
        mode: {
            type: "string",
            minLength: 1
        },
        cwd: {
            type: "string",
            minLength: 1
        },
        mcp: {
            type: "object",
            additionalProperties: mcpConfigSchema
        },
        prompt: {
            type: "string",
            minLength: 1
        }
    },
    additionalProperties: false
};
const statusSchema = {
    type: "object",
    properties: {
        state: {
            type: "string",
            enum: ["in_progress", "review", "completed"]
        },
        round: {
            type: "integer",
            minimum: 0
        },
        review_turn: {
            type: "integer",
            minimum: 0
        },
        reason: {
            type: "string",
            minLength: 1
        }
    },
    required: ["state", "round", "review_turn"],
    additionalProperties: false
};
export const superintendentDocumentSchema = {
    $schema: "https://json-schema.org/draft/2020-12/schema",
    $id: superintendentDocumentSchemaId,
    title: "Superintendent plan document",
    type: "object",
    properties: {
        $schema: {
            type: "string",
            const: superintendentDocumentSchemaId
        },
        kind: {
            type: "string",
            const: "superintendent"
        },
        version: {
            type: "integer",
            minimum: 1
        },
        extends: {
            type: "string",
            minLength: 1
        },
        mcp: {
            type: "object",
            additionalProperties: mcpConfigSchema
        },
        builder: agentRoleSchema,
        inspectors: {
            type: "object",
            additionalProperties: agentRoleSchema
        },
        superintendent: agentRoleSchema,
        owner: agentRoleSchema,
        max_rounds: {
            type: "integer",
            minimum: 1,
            default: 100
        },
        status: statusSchema
    },
    required: ["kind", "version", "builder", "superintendent", "owner", "status"],
    additionalProperties: false
};
export const superintendentBaseDocumentSchema = {
    $schema: "https://json-schema.org/draft/2020-12/schema",
    $id: superintendentBaseDocumentSchemaId,
    title: "Superintendent base document",
    type: "object",
    properties: {
        $schema: {
            type: "string",
            const: superintendentBaseDocumentSchemaId
        },
        kind: {
            type: "string",
            const: "superintendent-base"
        },
        version: {
            type: "integer",
            minimum: 1
        },
        extends: {
            type: "string",
            minLength: 1
        },
        mcp: {
            type: "object",
            additionalProperties: mcpConfigSchema
        },
        builder: partialAgentRoleSchema,
        inspectors: {
            type: "object",
            additionalProperties: partialAgentRoleSchema
        },
        superintendent: partialAgentRoleSchema,
        owner: partialAgentRoleSchema,
        max_rounds: {
            type: "integer",
            minimum: 1
        }
    },
    required: ["kind", "version"],
    additionalProperties: false
};
const validStatusStates = new Set(["in_progress", "review", "completed"]);
const maxSuperintendentExtendsDepth = 5;
export function parseSuperintendentDoc(filePath, content) {
    const resolvedFilePath = path.resolve(filePath);
    const parsed = parseSuperintendentFrontmatterDocument(resolvedFilePath, content);
    return {
        filePath: resolvedFilePath,
        body: parsed.body,
        frontmatter: parseFrontmatter(resolvedFilePath, parsed.frontmatter)
    };
}
export async function resolveSuperintendentDoc(filePath, content, fs) {
    const resolvedFilePath = path.resolve(filePath);
    const { body } = parseSuperintendentFrontmatterDocument(resolvedFilePath, content);
    const resolved = await resolveConfigExtends([
        {
            source: resolvedFilePath,
            filePath: resolvedFilePath,
            content
        }
    ], { fs });
    if (resolved.chain[1] !== undefined) {
        await assertSuperintendentBaseChain(resolvedFilePath, content, fs);
    }
    const { prompt: ignoredPrompt, ...frontmatter } = resolved.data;
    void ignoredPrompt;
    return {
        document: {
            filePath: resolvedFilePath,
            body,
            frontmatter: parseFrontmatter(resolvedFilePath, frontmatter)
        },
        ...(resolved.chain[1] === undefined ? {} : { extendsPath: resolved.chain[1] }),
        frontmatterData: frontmatter
    };
}
async function assertSuperintendentBaseChain(filePath, content, fs) {
    let currentPath = filePath;
    let currentContent = content;
    const visited = new Set([filePath]);
    for (let depth = 1; depth <= maxSuperintendentExtendsDepth; depth += 1) {
        const parsed = parseConfigDocument(currentContent, currentPath);
        if (parsed.extends === false || parsed.extends === true) {
            return;
        }
        const basePath = path.resolve(path.dirname(currentPath), parsed.extends);
        if (visited.has(basePath)) {
            return;
        }
        const baseContent = await fs.readFile(basePath, "utf8");
        const baseDocument = parseConfigDocument(baseContent, basePath);
        assertSuperintendentBaseFrontmatter(basePath, baseDocument.data);
        visited.add(basePath);
        currentPath = basePath;
        currentContent = baseContent;
    }
}
function assertSuperintendentBaseFrontmatter(filePath, frontmatter) {
    if (frontmatter.kind !== "superintendent-base") {
        throw new Error(`${filePath}: expected kind: superintendent-base`);
    }
    if (typeof frontmatter.version !== "number" ||
        !Number.isInteger(frontmatter.version) ||
        frontmatter.version < 1) {
        throw new Error(`${filePath}: version must be a positive integer`);
    }
    if (Object.prototype.hasOwnProperty.call(frontmatter, "status")) {
        throw new Error(`${filePath}: superintendent base must not define runtime status`);
    }
}
export function readExplicitBuilderAgent(filePath, content) {
    const resolvedFilePath = path.resolve(filePath);
    const parsed = parseSuperintendentFrontmatterDocument(resolvedFilePath, content);
    const frontmatter = expectRecord(parsed.frontmatter, "frontmatter", resolvedFilePath);
    if (frontmatter.builder === undefined) {
        return undefined;
    }
    const builder = expectRecord(frontmatter.builder, "builder", resolvedFilePath);
    return builder.agent === undefined
        ? undefined
        : expectString(builder.agent, "builder.agent", resolvedFilePath);
}
function parseSuperintendentFrontmatterDocument(filePath, content) {
    try {
        const parsed = parseFrontmatterDocument(content);
        if (parsed.body === content &&
            Object.keys(parsed.frontmatter).length === 0 &&
            !content.startsWith("\uFEFF---\n") &&
            !content.startsWith("---\n") &&
            !content.startsWith("---\r")) {
            throw new Error(`${filePath}: expected YAML frontmatter delimited by ---`);
        }
        if (parsed.errors.length > 0) {
            throw new Error(`${filePath}: invalid YAML frontmatter: ${parsed.errors[0]?.message}`);
        }
        return {
            frontmatter: parsed.frontmatter,
            body: parsed.body
        };
    }
    catch (error) {
        if (error instanceof FrontmatterParseError &&
            error.message === "Missing YAML frontmatter end delimiter (---).") {
            throw new Error(`${filePath}: missing YAML frontmatter end delimiter (---)`);
        }
        throw error;
    }
}
function parseFrontmatter(filePath, value) {
    const frontmatter = expectRecord(value, "frontmatter", filePath);
    assertOnlyKeys(frontmatter, "frontmatter", [
        "$schema",
        "kind",
        "version",
        "extends",
        "mcp",
        "builder",
        "inspectors",
        "superintendent",
        "owner",
        "max_rounds",
        "status"
    ], filePath);
    const kind = frontmatter.kind;
    if (kind === undefined) {
        throw new Error(`${filePath}: missing \`kind\` field in frontmatter`);
    }
    if (kind !== "superintendent") {
        throw new Error(`${filePath}: frontmatter kind must be "superintendent"`);
    }
    return {
        kind,
        version: expectPositiveInteger(frontmatter.version, "version", filePath),
        mcp: parseMcpMap(frontmatter.mcp, filePath),
        builder: parseRequiredRole(frontmatter.builder, "builder", filePath),
        inspectors: parseInspectorMap(frontmatter.inspectors, filePath),
        superintendent: parseRequiredRole(frontmatter.superintendent, "superintendent", filePath),
        owner: parseRequiredRole(frontmatter.owner, "owner", filePath),
        max_rounds: frontmatter.max_rounds === undefined
            ? 100
            : expectPositiveInteger(frontmatter.max_rounds, "max_rounds", filePath),
        status: parseStatusBlock(frontmatter.status, filePath)
    };
}
function parseRequiredRole(value, roleName, filePath) {
    if (value === undefined) {
        throw new Error(`${filePath}: missing required role \`${roleName}\``);
    }
    const role = expectRecord(value, roleName, filePath);
    assertOnlyKeys(role, roleName, ["agent", "mode", "cwd", "mcp", "prompt"], filePath);
    const mcp = role.mcp === undefined ? undefined : parseMcpMap(role.mcp, filePath, `${roleName}.mcp`);
    return {
        agent: role.agent === undefined
            ? "claude-code"
            : expectNonEmptyString(role.agent, `${roleName}.agent`, filePath),
        mode: role.mode === undefined
            ? undefined
            : expectNonEmptyString(role.mode, `${roleName}.mode`, filePath),
        cwd: role.cwd === undefined
            ? undefined
            : expectNonEmptyString(role.cwd, `${roleName}.cwd`, filePath),
        mcp,
        prompt: expectNonEmptyString(role.prompt, `${roleName}.prompt`, filePath)
    };
}
function parseInspectorMap(value, filePath) {
    if (value === undefined) {
        return undefined;
    }
    const inspectors = expectRecord(value, "inspectors", filePath);
    return Object.fromEntries(Object.entries(inspectors).map(([name, config]) => {
        assertValidInspectorName(name, filePath);
        return [
            name,
            parseRequiredRole(config, `inspectors.${name}`, filePath)
        ];
    }));
}
function parseMcpMap(value, filePath, fieldName = "mcp") {
    if (value === undefined) {
        return undefined;
    }
    const mcp = expectRecord(value, fieldName, filePath);
    return Object.fromEntries(Object.entries(mcp).map(([name, config]) => [
        name,
        parseMcpConfig(config, `${fieldName}.${name}`, filePath)
    ]));
}
function parseMcpConfig(value, fieldName, filePath) {
    const config = expectRecord(value, fieldName, filePath);
    assertOnlyKeys(config, fieldName, ["command", "args", "timeout"], filePath);
    return {
        command: expectNonEmptyString(config.command, `${fieldName}.command`, filePath),
        args: config.args === undefined
            ? undefined
            : expectStringArray(config.args, `${fieldName}.args`, filePath),
        timeout: config.timeout === undefined
            ? undefined
            : expectPositiveNumber(config.timeout, `${fieldName}.timeout`, filePath)
    };
}
function parseStatusBlock(value, filePath) {
    const status = expectRecord(value, "status", filePath);
    assertOnlyKeys(status, "status", ["state", "round", "review_turn", "reason"], filePath);
    const state = expectString(status.state, "status.state", filePath);
    if (!validStatusStates.has(state)) {
        throw new Error(`${filePath}: status.state must be one of in_progress, review, completed`);
    }
    const parsedState = state;
    return {
        state: parsedState,
        round: expectNonNegativeInteger(status.round, "status.round", filePath),
        review_turn: expectNonNegativeInteger(status.review_turn, "status.review_turn", filePath)
    };
}
function expectRecord(value, fieldName, filePath) {
    if (!isRecord(value)) {
        throw new Error(`${filePath}: ${fieldName} must be a mapping`);
    }
    return value;
}
function assertOnlyKeys(value, fieldName, allowedKeys, filePath) {
    const allowed = new Set(allowedKeys);
    for (const key of Object.keys(value)) {
        if (!allowed.has(key)) {
            throw new Error(`${filePath}: unknown field ${fieldName}.${key}`);
        }
    }
}
function expectString(value, fieldName, filePath) {
    if (typeof value !== "string") {
        throw new Error(`${filePath}: ${fieldName} must be a string`);
    }
    return value;
}
function expectNonEmptyString(value, fieldName, filePath) {
    if (typeof value !== "string" || value.trim().length === 0) {
        throw new Error(`${filePath}: ${fieldName} must be a non-empty string`);
    }
    return value;
}
function expectPositiveInteger(value, fieldName, filePath) {
    if (!Number.isInteger(value) || value <= 0) {
        throw new Error(`${filePath}: ${fieldName} must be a positive integer`);
    }
    return value;
}
function expectNonNegativeInteger(value, fieldName, filePath) {
    if (!Number.isInteger(value) || value < 0) {
        throw new Error(`${filePath}: ${fieldName} must be a non-negative integer`);
    }
    return value;
}
function expectPositiveNumber(value, fieldName, filePath) {
    if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
        throw new Error(`${filePath}: ${fieldName} must be a positive number`);
    }
    return value;
}
function expectStringArray(value, fieldName, filePath) {
    if (!Array.isArray(value) ||
        value.some((item) => typeof item !== "string" || item.trim().length === 0)) {
        throw new Error(`${filePath}: ${fieldName} must be an array of non-empty strings`);
    }
    return value;
}
function assertValidInspectorName(name, filePath) {
    if (!isTemplateReferenceName(name)) {
        throw new Error(`${filePath}: inspectors.${name} name must use only letters, numbers, underscores, or hyphens`);
    }
}
function isTemplateReferenceName(value) {
    if (value.length === 0) {
        return false;
    }
    for (const character of value) {
        const code = character.charCodeAt(0);
        const isNumber = code >= 48 && code <= 57;
        const isUpper = code >= 65 && code <= 90;
        const isLower = code >= 97 && code <= 122;
        if (!isNumber && !isUpper && !isLower && character !== "_" && character !== "-") {
            return false;
        }
    }
    return true;
}
function isRecord(value) {
    return typeof value === "object" && value !== null && !Array.isArray(value);
}
