"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateArtifactShape = validateArtifactShape;
exports.parseArtifactMarkdown = parseArtifactMarkdown;
const artifact_family_1 = require("./artifact-family");
const planning_artifact_document_1 = require("../planning/planning-artifact-document");
const REQUIRED_H2_BY_ARTIFACT_TYPE = new Map([
    [
        "slice_overview",
        [
            "Purpose",
            "Source Specification",
            "Desired Outcome",
            "Scope",
            "Non-Goals",
            "Planning Depth",
            "Phases",
            "Decisions",
            "Risks",
            "Validation Strategy",
            "Open Questions"
        ]
    ],
    [
        "phase_overview",
        [
            "Purpose",
            "Scope",
            "Milestones",
            "Acceptance Criteria",
            "Dependencies",
            "Risks",
            "Validation Focus",
            "Open Questions"
        ]
    ],
    [
        "milestone_overview",
        [
            "Purpose",
            "Scope",
            "Tasks",
            "Acceptance Criteria",
            "Dependencies",
            "Risks",
            "Validation Focus",
            "Open Questions"
        ]
    ],
    [
        "task_definition",
        [
            "Objective",
            "Upstream Context",
            "Implementation Target Paths",
            "Current Behavior",
            "Desired Behavior",
            "Runtime Delta",
            "Acceptance Criteria",
            "Validation Commands",
            "Rollback / Recovery",
            "Risks",
            "Dependencies",
            "Constraints",
            "Assumptions",
            "Lifecycle / Closure Expectations",
            "Open Questions"
        ]
    ],
    [
        "implementation_summary",
        [
            "Summary",
            "Changed Paths",
            "Acceptance Criteria Results",
            "Validation Performed",
            "Scope Classification",
            "Deviations",
            "Risks and Follow-Ups",
            "Open Questions"
        ]
    ],
    [
        "reconciliation",
        [
            "Review Status",
            "Milestone Intent",
            "Completed Task Summaries",
            "Acceptance Criteria Review",
            "Planned Work",
            "Discovered Work",
            "Backlog Work",
            "Accepted Residual Work",
            "Validation Review",
            "Follow-Up Tasks",
            "Review Concerns",
            "Final Notes"
        ]
    ]
]);
const TEMPLATE_H2_BY_PATH = new Map([
    [
        ".day-shift/templates/task-definition.md",
        REQUIRED_H2_BY_ARTIFACT_TYPE.get("task_definition") ?? []
    ],
    [
        ".day-shift/templates/implementation-summary.md",
        REQUIRED_H2_BY_ARTIFACT_TYPE.get("implementation_summary") ?? []
    ],
    [
        ".day-shift/templates/milestone-reconciliation.md",
        REQUIRED_H2_BY_ARTIFACT_TYPE.get("reconciliation") ?? []
    ]
]);
const PLACEHOLDER_PATTERNS = [
    /\{\{\s*[A-Za-z_][A-Za-z0-9_.-]*\s*\}\}/,
    /\bTBD\b/i,
    /\bTODO\b/i,
    /^Pending\./im,
    /^None yet\./im,
    /^Not started\./im
];
const SCAFFOLD_PROSE_PHRASES = [
    "Template notes:",
    "Use this section to",
    "List only",
    "Keep only",
    "If there are none, use exactly",
    "This implementation summary is initialized as the stable execution artifact for the task.",
    "This reconciliation artifact is initialized as a milestone-generation placeholder"
];
const PROMPT_REQUIRED_FRONT_MATTER_FIELDS = ["prompt_version", "layer", "action"];
const PROMPT_LAYER_VALUES = new Set([
    "specification",
    "slice",
    "phase",
    "milestone",
    "task",
    "implementation",
    "reconciliation"
]);
const PROMPT_ACTION_VALUES = new Set(["build", "next", "review"]);
const PROMPT_FRONT_MATTER_FIELD_SET = new Set(PROMPT_REQUIRED_FRONT_MATTER_FIELDS);
function validateArtifactShape(input) {
    const family = (0, artifact_family_1.detectArtifactFamily)(input);
    const findings = [];
    if (!family.supported) {
        findings.push(createFinding("UNSUPPORTED_ARTIFACT_FAMILY", input.path, family.reason));
        return {
            path: family.path,
            family,
            markdown: null,
            json: null,
            findings
        };
    }
    if (family.contentFormat === "json") {
        return validateJsonArtifact(input.path, input.content, family, findings);
    }
    if (family.contentFormat !== "markdown") {
        return {
            path: family.path,
            family,
            markdown: null,
            json: null,
            findings
        };
    }
    const markdown = parseArtifactMarkdown(input.content);
    if (family.metadataBearing && !markdown.hasFrontMatter) {
        findings.push(createFinding("MISSING_FRONT_MATTER", family.path, "Artifact is missing required YAML front matter."));
    }
    if (family.metadataBearing && markdown.malformedFrontMatter) {
        findings.push(createFinding("MALFORMED_FRONT_MATTER", family.path, "Artifact YAML front matter is not properly closed."));
    }
    if (markdown.hasFrontMatter && !markdown.malformedFrontMatter) {
        validateFamilySpecificFrontMatter(family, markdown.frontMatterText, findings);
    }
    validateVisibleHeadingShape(family, markdown.bodyText, findings);
    if (!family.sourceTemplate && !family.scaffoldSource) {
        validatePlaceholderResidue(family.path, markdown.bodyText, findings);
        validateScaffoldProse(family.path, markdown.bodyText, findings);
    }
    return {
        path: family.path,
        family,
        markdown,
        json: null,
        findings
    };
}
function validateFamilySpecificFrontMatter(family, frontMatterText, findings) {
    const frontMatter = readFlatFrontMatter(frontMatterText);
    if (family.family === "prompt") {
        validatePromptFrontMatter(family.path, frontMatter, findings);
        return;
    }
    if (family.family === "template") {
        validateTemplateFrontMatter(family.path, frontMatter, findings);
    }
}
function validatePromptFrontMatter(artifactPath, frontMatter, findings) {
    for (const field of PROMPT_REQUIRED_FRONT_MATTER_FIELDS) {
        if (!frontMatter.has(field) || (frontMatter.get(field) ?? "").trim() === "") {
            findings.push(createFinding("MISSING_PROMPT_FRONT_MATTER_FIELD", artifactPath, `Prompt artifact is missing required front matter field '${field}'.`));
        }
    }
    const promptVersion = frontMatter.get("prompt_version");
    if (promptVersion !== undefined && !/^[1-9][0-9]*$/u.test(promptVersion)) {
        findings.push(createFinding("INVALID_PROMPT_FRONT_MATTER_FIELD", artifactPath, "Prompt front matter field 'prompt_version' must be a positive integer."));
    }
    const layer = frontMatter.get("layer");
    if (layer !== undefined && !PROMPT_LAYER_VALUES.has(layer)) {
        findings.push(createFinding("INVALID_PROMPT_FRONT_MATTER_FIELD", artifactPath, `Prompt front matter field 'layer' has unsupported value '${layer}'.`));
    }
    const action = frontMatter.get("action");
    if (action !== undefined && !PROMPT_ACTION_VALUES.has(action)) {
        findings.push(createFinding("INVALID_PROMPT_FRONT_MATTER_FIELD", artifactPath, `Prompt front matter field 'action' has unsupported value '${action}'.`));
    }
}
function validateTemplateFrontMatter(artifactPath, frontMatter, findings) {
    for (const field of PROMPT_FRONT_MATTER_FIELD_SET) {
        if (frontMatter.has(field)) {
            findings.push(createFinding("INVALID_TEMPLATE_FRONT_MATTER_FIELD", artifactPath, `Template artifact front matter must not use prompt-family field '${field}'.`));
        }
    }
}
function readFlatFrontMatter(frontMatterText) {
    const values = new Map();
    for (const line of frontMatterText.split(/\r?\n/)) {
        const match = line.match(/^([A-Za-z0-9_]+):\s*(.*)$/u);
        if (match === null) {
            continue;
        }
        values.set(match[1], unquoteScalar(match[2].trim()));
    }
    return values;
}
function unquoteScalar(value) {
    if ((value.startsWith("\"") && value.endsWith("\"")) ||
        (value.startsWith("'") && value.endsWith("'"))) {
        return value.slice(1, -1);
    }
    return value;
}
function parseArtifactMarkdown(markdownText) {
    const document = (0, planning_artifact_document_1.parsePlanningArtifactDocument)(markdownText);
    if (!document.hasFrontMatter) {
        const malformed = document.diagnostics.some((diagnostic) => diagnostic.code === "unterminated-front-matter");
        return {
            frontMatterText: malformed ? markdownText.split(/\r?\n/).slice(1).join("\n") : "",
            bodyText: markdownText,
            hasFrontMatter: false,
            malformedFrontMatter: malformed
        };
    }
    return {
        frontMatterText: document.frontMatterLines.join("\n"),
        bodyText: document.bodyText.split(/\r?\n/).join("\n"),
        hasFrontMatter: true,
        malformedFrontMatter: false
    };
}
function validateJsonArtifact(artifactPath, content, family, findings) {
    try {
        const json = JSON.parse(content);
        validateManifestJsonShape(family, json, findings);
        return {
            path: family.path,
            family,
            markdown: null,
            json,
            findings
        };
    }
    catch {
        findings.push(createFinding("MALFORMED_JSON_MANIFEST", artifactPath, "JSON manifest content is malformed and was not parsed."));
        return {
            path: family.path,
            family,
            markdown: null,
            json: null,
            findings
        };
    }
}
function validateManifestJsonShape(family, json, findings) {
    if (!isPlainObject(json)) {
        findings.push(createFinding("MALFORMED_JSON_MANIFEST", family.path, "JSON manifest content must be an object."));
        return;
    }
    if (family.family === "prompt-manifest") {
        validatePromptManifestJson(family.path, json, findings);
        return;
    }
    if (family.family === "template-manifest") {
        validateTemplateManifestJson(family.path, json, findings);
    }
}
function validatePromptManifestJson(artifactPath, manifest, findings) {
    if (typeof manifest.prompt_manifest_version !== "number") {
        findings.push(createFinding("MISSING_PROMPT_MANIFEST_FIELD", artifactPath, "Prompt manifest is missing numeric 'prompt_manifest_version'.", "warning"));
    }
    if (typeof manifest.prompt_version !== "number") {
        findings.push(createFinding("MISSING_PROMPT_MANIFEST_FIELD", artifactPath, "Prompt manifest is missing numeric top-level 'prompt_version'.", "warning"));
    }
    if (!Array.isArray(manifest.prompts)) {
        findings.push(createFinding("MISSING_PROMPT_MANIFEST_FIELD", artifactPath, "Prompt manifest is missing required 'prompts' array."));
        return;
    }
    manifest.prompts.forEach((entry, index) => {
        const entryPath = isPlainObject(entry) && typeof entry.path === "string"
            ? entry.path
            : `${artifactPath}#prompts[${index}]`;
        if (!isPlainObject(entry)) {
            findings.push(createFinding("INVALID_PROMPT_MANIFEST_FIELD", entryPath, "Prompt manifest entry must be an object."));
            return;
        }
        for (const field of ["path", "layer", "action"]) {
            if (typeof entry[field] !== "string" || entry[field] === "") {
                findings.push(createFinding("MISSING_PROMPT_MANIFEST_FIELD", entryPath, `Prompt manifest entry is missing string '${field}'.`, "warning"));
            }
        }
        if (typeof entry.version !== "number") {
            findings.push(createFinding("MISSING_PROMPT_MANIFEST_FIELD", entryPath, "Prompt manifest entry is missing numeric 'version'.", "warning"));
        }
        if (entry.template_ref !== undefined && typeof entry.template_ref !== "string") {
            findings.push(createFinding("INVALID_PROMPT_MANIFEST_FIELD", entryPath, "Prompt manifest entry field 'template_ref' must be a string."));
        }
        if (entry.template_refs !== undefined && !isStringArray(entry.template_refs)) {
            findings.push(createFinding("INVALID_PROMPT_MANIFEST_FIELD", entryPath, "Prompt manifest entry field 'template_refs' must be an array of strings."));
        }
        if (entry.template_ref !== undefined && entry.template_refs !== undefined) {
            findings.push(createFinding("INVALID_PROMPT_MANIFEST_FIELD", entryPath, "Prompt manifest entry must preserve either 'template_ref' or 'template_refs', not both."));
        }
        if (entry.destination !== undefined) {
            findings.push(createFinding("INVALID_PROMPT_MANIFEST_FIELD", entryPath, "Prompt manifest entries must not declare template-manifest destination routing."));
        }
    });
}
function validateTemplateManifestJson(artifactPath, manifest, findings) {
    if (typeof manifest.template_manifest_version !== "number") {
        findings.push(createFinding("MISSING_TEMPLATE_MANIFEST_FIELD", artifactPath, "Template manifest is missing numeric 'template_manifest_version'.", "warning"));
    }
    if (typeof manifest.template_version !== "number") {
        findings.push(createFinding("MISSING_TEMPLATE_MANIFEST_FIELD", artifactPath, "Template manifest is missing numeric top-level 'template_version'.", "warning"));
    }
    if (!Array.isArray(manifest.templates)) {
        findings.push(createFinding("MISSING_TEMPLATE_MANIFEST_FIELD", artifactPath, "Template manifest is missing required 'templates' array."));
        return;
    }
    manifest.templates.forEach((entry, index) => {
        const entryPath = isPlainObject(entry) && typeof entry.path === "string"
            ? entry.path
            : `${artifactPath}#templates[${index}]`;
        if (!isPlainObject(entry)) {
            findings.push(createFinding("INVALID_TEMPLATE_MANIFEST_FIELD", entryPath, "Template manifest entry must be an object."));
            return;
        }
        for (const field of ["path", "kind", "artifact_type"]) {
            if (typeof entry[field] !== "string" || entry[field] === "") {
                findings.push(createFinding("MISSING_TEMPLATE_MANIFEST_FIELD", entryPath, `Template manifest entry is missing string '${field}'.`, "warning"));
            }
        }
        if (typeof entry.version !== "number") {
            findings.push(createFinding("MISSING_TEMPLATE_MANIFEST_FIELD", entryPath, "Template manifest entry is missing numeric 'version'.", "warning"));
        }
        if (entry.template_ref !== undefined || entry.template_refs !== undefined) {
            findings.push(createFinding("INVALID_TEMPLATE_MANIFEST_FIELD", entryPath, "Template manifest entries must not use prompt-family template_ref or template_refs fields."));
        }
        if (entry.destination !== undefined) {
            if (typeof entry.destination !== "string" || entry.destination === "") {
                findings.push(createFinding("INVALID_SCAFFOLD_DESTINATION_FIELD", entryPath, "Template manifest entry field 'destination' must be a non-empty string."));
            }
            if (entry.kind !== "scaffold") {
                findings.push(createFinding("INVALID_SCAFFOLD_DESTINATION_FIELD", entryPath, "Template manifest destination routing is allowed only for scaffold entries."));
            }
        }
    });
}
function validateVisibleHeadingShape(family, bodyText, findings) {
    const h1Headings = collectHeadings(bodyText, 1);
    if (h1Headings.length === 0) {
        findings.push(createFinding("MISSING_H1", family.path, "Artifact body must include exactly one visible H1 heading."));
    }
    if (h1Headings.length > 1) {
        findings.push(createFinding("MULTIPLE_H1", family.path, "Artifact body must not include more than one visible H1 heading."));
    }
    const requiredH2 = getRequiredH2Contract(family);
    if (requiredH2.length === 0) {
        return;
    }
    const observedH2 = collectHeadings(bodyText, 2);
    let searchStart = 0;
    for (const expected of requiredH2) {
        const index = observedH2.findIndex((heading, headingIndex) => headingIndex >= searchStart && heading === expected);
        if (index < 0) {
            findings.push(createFinding("MISSING_REQUIRED_H2", family.path, `Artifact is missing required H2 heading '${expected}'.`));
            continue;
        }
        if (index < searchStart) {
            findings.push(createFinding("OUT_OF_ORDER_REQUIRED_H2", family.path, `Required H2 heading '${expected}' is out of order.`));
            continue;
        }
        searchStart = index + 1;
    }
}
function validatePlaceholderResidue(artifactPath, bodyText, findings) {
    for (const pattern of PLACEHOLDER_PATTERNS) {
        if (pattern.test(bodyText)) {
            findings.push(createFinding("UNRESOLVED_PLACEHOLDER", artifactPath, "Artifact body contains unresolved placeholder text."));
            return;
        }
    }
}
function validateScaffoldProse(artifactPath, bodyText, findings) {
    for (const phrase of SCAFFOLD_PROSE_PHRASES) {
        if (bodyText.includes(phrase)) {
            findings.push(createFinding("SCAFFOLD_PROSE_LEAK", artifactPath, `Artifact body contains scaffold instruction prose '${phrase}'.`));
            return;
        }
    }
}
function getRequiredH2Contract(family) {
    if (family.artifactType !== null) {
        return REQUIRED_H2_BY_ARTIFACT_TYPE.get(family.artifactType) ?? [];
    }
    if (family.sourceTemplate) {
        return TEMPLATE_H2_BY_PATH.get(family.path) ?? [];
    }
    return [];
}
function collectHeadings(markdownText, level) {
    const prefix = "#".repeat(level);
    const pattern = new RegExp(`^${prefix} (.+)$`);
    const headings = [];
    for (const line of markdownText.split(/\r?\n/)) {
        const match = line.match(pattern);
        if (match !== null) {
            headings.push(match[1].trim());
        }
    }
    return headings;
}
function createFinding(code, artifactPath, message, severity = "error") {
    return {
        code,
        severity,
        path: artifactPath.replace(/\\/g, "/").replace(/^\.\//, ""),
        message
    };
}
function isPlainObject(value) {
    return typeof value === "object" && value !== null && !Array.isArray(value);
}
function isStringArray(value) {
    return Array.isArray(value) && value.every((entry) => typeof entry === "string");
}
