<?php
/**
 * Walks the shipped seed corpus under `data/seed/` and yields one record per
 * file. The loader is intentionally minimal: no Symfony YAML dependency, no
 * file globbing through Composer — just a recursive directory iterator plus a
 * hand-rolled front-matter split.
 *
 * Each `.md` file MUST begin with a YAML-ish front matter block bounded by
 * `---` lines. Recognized keys:
 *
 *   id           Stable slug. Defaults to the file path relative to data/seed/
 *                without the .md extension.
 *   title        Human-facing label surfaced on the `sources_used` SSE event.
 *   source_type  One of seed_archetype | seed_pattern | seed_convention | seed_copy.
 *                Defaults to seed_doc.
 *   priority     hot | warm | cold — controls bootstrap order so hero/pricing/
 *                cta archetypes index first.
 *
 * @package Antimanual_Builder
 * @since   1.7.0
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class AMBLD_Rag_Seed_Loader {

	const SEED_DIR_REL = 'data/seed/';

	/** Priority order used to sort records before embedding. */
	const PRIORITY_ORDER = array( 'hot' => 0, 'warm' => 1, 'cold' => 2 );

	/**
	 * Absolute path to the shipped seed directory.
	 */
	public static function seed_dir() {
		return AMBLD_PLUGIN_DIR . self::SEED_DIR_REL;
	}

	/**
	 * Walk the seed directory and return an array of record arrays.
	 *
	 * Sorted by priority so the cron handler can embed `hot` items first —
	 * useful when the embedding pass runs in batches.
	 *
	 * @return array<int, array{source_id:string, source_type:string, source_label:string, content:string, content_hash:string, priority:string, path:string}>
	 */
	public static function load_all() {
		$dir = self::seed_dir();
		if ( ! is_dir( $dir ) ) {
			return array();
		}

		$records = array();
		$rii     = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator( $dir, \FilesystemIterator::SKIP_DOTS ) );
		foreach ( $rii as $file_info ) {
			if ( ! $file_info->isFile() ) {
				continue;
			}
			if ( 'md' !== strtolower( $file_info->getExtension() ) ) {
				continue;
			}
			$path     = $file_info->getPathname();
			$contents = @file_get_contents( $path ); // phpcs:ignore WordPress.WP.AlternativeFunctions, WordPress.PHP.NoSilencedErrors
			if ( ! is_string( $contents ) || '' === trim( $contents ) ) {
				continue;
			}

			$parsed = self::parse_front_matter( $contents );
			$body   = trim( $parsed['body'] );
			if ( '' === $body ) {
				continue;
			}

			$rel_path  = str_replace( '\\', '/', substr( $path, strlen( $dir ) ) );
			$rel_slug  = preg_replace( '/\.md$/i', '', $rel_path );

			$meta        = $parsed['meta'];
			$source_id   = ! empty( $meta['id'] ) ? (string) $meta['id'] : 'seed:' . $rel_slug;
			$source_type = ! empty( $meta['source_type'] ) ? self::sanitize_source_type( $meta['source_type'] ) : 'seed_doc';
			$label       = ! empty( $meta['title'] ) ? (string) $meta['title'] : $rel_slug;
			$priority    = isset( $meta['priority'] ) && isset( self::PRIORITY_ORDER[ $meta['priority'] ] )
				? (string) $meta['priority']
				: 'warm';

			// Hash the BODY (not the front matter) so editorial-only changes
			// to the front matter don't trigger a needless re-embed.
			$records[] = array(
				'source_id'    => $source_id,
				'source_type'  => $source_type,
				'source_label' => $label,
				'content'      => $body,
				'content_hash' => sha1( $body ),
				'priority'     => $priority,
				'path'         => $path,
			);
		}

		usort(
			$records,
			static function ( $a, $b ) {
				$pa = self::PRIORITY_ORDER[ $a['priority'] ] ?? 1;
				$pb = self::PRIORITY_ORDER[ $b['priority'] ] ?? 1;
				if ( $pa === $pb ) {
					return strcmp( $a['source_id'], $b['source_id'] );
				}
				return $pa < $pb ? -1 : 1;
			}
		);

		return $records;
	}

	/**
	 * Hand-rolled YAML-ish parser. Handles flat `key: value` pairs only —
	 * intentional, the seed corpus has no nested metadata. Quoted values are
	 * supported so titles containing colons round-trip.
	 *
	 * @param string $contents Full file contents.
	 * @return array{meta:array<string,string>, body:string}
	 */
	private static function parse_front_matter( $contents ) {
		$contents = preg_replace( "/^\xEF\xBB\xBF/", '', $contents ); // strip BOM
		$contents = str_replace( "\r\n", "\n", (string) $contents );

		if ( 0 !== strpos( $contents, "---\n" ) ) {
			return array( 'meta' => array(), 'body' => $contents );
		}

		$end = strpos( $contents, "\n---\n", 4 );
		if ( false === $end ) {
			$end = strpos( $contents, "\n---", 4 );
			if ( false === $end ) {
				return array( 'meta' => array(), 'body' => $contents );
			}
		}

		$header = substr( $contents, 4, $end - 4 );
		$body   = substr( $contents, $end );
		$body   = ltrim( $body, "-\n" );

		$meta = array();
		foreach ( explode( "\n", $header ) as $line ) {
			$line = trim( $line );
			if ( '' === $line || 0 === strpos( $line, '#' ) ) {
				continue;
			}
			$pos = strpos( $line, ':' );
			if ( false === $pos ) {
				continue;
			}
			$key = strtolower( trim( substr( $line, 0, $pos ) ) );
			$val = trim( substr( $line, $pos + 1 ) );
			$val = trim( $val, "\"'" );
			$meta[ $key ] = $val;
		}

		return array( 'meta' => $meta, 'body' => $body );
	}

	/**
	 * Whitelist source_type to the seed_* namespace. Unknown values fall back
	 * to `seed_doc` so a typo in a single file can't pollute the schema.
	 */
	private static function sanitize_source_type( $value ) {
		$value = strtolower( trim( (string) $value ) );
		$allowed = array(
			'seed_archetype',
			'seed_pattern',
			'seed_convention',
			'seed_copy',
			'seed_doc',
		);
		return in_array( $value, $allowed, true ) ? $value : 'seed_doc';
	}
}
