/**
 * skill-author/validate.mjs — C4 SKILL.md validator (pure fn).
 *
 * Grep-style validation that mirrors the rigor of the existing per-skill
 * structural tests (see apt-design-skill-structure.test.ts). We do NOT build
 * a full markdown AST — we check:
 *
 *   1. YAML frontmatter block is parseable + passes SkillFrontmatterSchema.
 *   2. Every REQUIRED_SECTIONS tag has a matching opening + closing pair.
 *   3. The <process> body contains at least two numbered step headings
 *      (`## 1. Foo`, `## 2. Bar`, ...) with monotonic numbering from 1.
 *   4. Soft-required tags either present or emit a warning.
 *
 * Returns { ok, errors, warnings } — callers decide whether to treat warnings
 * as a hard failure (the integration smoke test does; the CLI does not).
 */

import { parse as parseYaml } from 'yaml'
import { REQUIRED_SECTIONS, SOFT_REQUIRED_SECTIONS, validateFrontmatter } from './contract.mjs'

const FRONTMATTER_RE = /^---\n([\s\S]*?)\n---\n/

/**
 * @typedef {{ ok: boolean, errors: string[], warnings: string[] }} ValidationResult
 */

/**
 * Validate a SKILL.md's raw text against the canonical contract.
 *
 * @param {string} text — full SKILL.md contents, including frontmatter
 * @returns {ValidationResult}
 */
export function validateSkillMarkdown(text) {
	const errors = []
	const warnings = []

	if (typeof text !== 'string' || text.length === 0) {
		return { ok: false, errors: ['SKILL.md is empty or not a string'], warnings }
	}

	// --- Frontmatter ---------------------------------------------------------
	const fmMatch = text.match(FRONTMATTER_RE)
	if (!fmMatch) {
		errors.push('missing YAML frontmatter block (must start with `---\\n...\\n---\\n`)')
	} else {
		let parsed
		try {
			parsed = parseYaml(fmMatch[1])
		} catch (err) {
			errors.push(`frontmatter YAML parse error: ${(err && err.message) || String(err)}`)
			parsed = null
		}
		if (parsed !== null) {
			if (parsed === undefined || typeof parsed !== 'object') {
				errors.push('frontmatter must be a YAML object')
			} else {
				const res = validateFrontmatter(parsed)
				if (!res.success) {
					for (const issue of res.error.issues) {
						const pathStr = issue.path && issue.path.length > 0 ? issue.path.join('.') : '(root)'
						errors.push(`frontmatter.${pathStr}: ${issue.message}`)
					}
				}
			}
		}
	}

	// --- Required section tags ----------------------------------------------
	for (const tag of REQUIRED_SECTIONS) {
		const closing = tag.replace('<', '</')
		if (!text.includes(tag)) {
			errors.push(`missing required opening tag ${tag}`)
		}
		if (!text.includes(closing)) {
			errors.push(`missing required closing tag ${closing}`)
		}
	}

	// --- Process block shape (numbered steps, monotonic from 1) --------------
	const processMatch = text.match(/<process>([\s\S]*?)<\/process>/)
	if (processMatch) {
		const body = processMatch[1]
		const stepRe = /^##\s+(\d+(?:\.\d+)?)\.\s+/gm
		const steps = []
		let m
		m = stepRe.exec(body)
		while (m !== null) {
			steps.push(m[1])
			m = stepRe.exec(body)
		}
		if (steps.length < 2) {
			errors.push(
				`<process> must contain at least two numbered step headings (e.g. "## 1. Load Context"); found ${steps.length}`,
			)
		} else {
			// Monotonic-from-1 check is a warning, not an error — apt:design
			// uses "## 2b. Render TASTE_BLOCK" and similar sub-step notation
			// that is legitimate but not strictly monotonic.
			const primaryNumbers = steps.map((s) => Number.parseInt(String(s).split('.')[0], 10))
			if (primaryNumbers[0] !== 1) {
				warnings.push(
					`<process> first step number is ${primaryNumbers[0]} but should be 1 for readability`,
				)
			}
			for (let i = 1; i < primaryNumbers.length; i++) {
				const prev = primaryNumbers[i - 1]
				const cur = primaryNumbers[i]
				if (cur !== prev && cur !== prev + 1) {
					warnings.push(
						`<process> step numbering jumps from ${prev} to ${cur}; prefer consecutive integers`,
					)
				}
			}
		}
	}

	// --- Soft-required tags (warnings only) ---------------------------------
	for (const tag of SOFT_REQUIRED_SECTIONS) {
		if (!text.includes(tag)) {
			warnings.push(
				`missing soft-required tag ${tag} — consider adding one for better agent behavior (see docs/skill-authoring.md)`,
			)
		}
	}

	return { ok: errors.length === 0, errors, warnings }
}
