<?php

/**
 * LolaCore - DistillDraftQuality
 *
 * Post-distillation quality checks before showing the draft to the user.
 *
 * @package LolaCore
 * @license GPL-2.0-or-later
 */

declare(strict_types=1);

namespace LolaCore\Playbooks;

use LolaCore\Playbooks\Exceptions\PlaybookParseError;
use LolaCore\Playbooks\Exceptions\PlaybookValidationError;

final class DistillDraftQuality
{
    /** @var array<int,string> */
    private const GENERIC_SLUGS = [
        'json',
        'xml',
        'code',
        'markdown',
        'md',
        'yaml',
        'yml',
        'distilled-playbook',
        'untitled',
        'document',
    ];

    public function __construct(
        private ?PlaybookLoader $loader = null
    ) {
        if ($this->loader === null) {
            $parser    = new \LolaCore\Skills\YamlParser();
            $validator = new PlaybookValidator();
            $this->loader = new PlaybookLoader($parser, $validator);
        }
    }

    /**
     * @return string|null Human-readable rejection reason, or null when acceptable.
     */
    public function assess(string $draft, int $sourceCharCount = 0): ?string
    {
        if (str_contains($draft, '<') && preg_match('/<[a-z-]+>/i', $draft) === 1) {
            return 'output still contains unfilled template placeholders';
        }

        $parsed = $this->loader->parsePlaybookContent($draft, Playbook::ORIGIN_DB, 'distill://draft');
        if ($parsed instanceof PlaybookParseError) {
            return $parsed->getUserMessage()
                . ' — keep frontmatter to name, description, domain, type only; double-quote description.';
        }
        if ($parsed instanceof PlaybookValidationError) {
            return $parsed->getMessage();
        }

        $slug = strtolower($parsed->getSlug());
        if (in_array($slug, self::GENERIC_SLUGS, true) || mb_strlen($slug) < 3) {
            return "playbook name '{$slug}' is too generic";
        }

        $description = trim($parsed->getDescription());
        if ($description === '' || mb_strlen($description) < 15) {
            return 'description is missing or too short';
        }
        if (preg_match('/^distilled from uploaded document$/i', $description) === 1) {
            return 'description is a placeholder, not a real summary';
        }

        $domain = strtolower(trim($parsed->getDomain()));
        if ($domain === '' || $domain === 'general') {
            return "domain '{$domain}' is too vague — use a specific domain slug";
        }

        $body = $parsed->getBody();
        foreach (PlaybookDistillTemplate::REQUIRED_BODY_SECTIONS as $section) {
            $pattern = '/^##\s+' . preg_quote($section, '/') . '\b/im';
            if (!preg_match($pattern, $body)) {
                return "body is missing a ## {$section} section";
            }
        }

        if ($this->looksLikeCodeDump($body)) {
            return 'body looks like a raw code/config dump instead of distilled prose';
        }

        $thinIssue = $this->assessThinDistillation($body, $sourceCharCount);
        if ($thinIssue !== null) {
            return $thinIssue;
        }

        return null;
    }

    /**
     * Large sources should produce dense playbooks — thin summaries waste the body budget.
     */
    private function assessThinDistillation(string $body, int $sourceCharCount): ?string
    {
        if ($sourceCharCount < 10000) {
            return null;
        }

        $bodyChars = mb_strlen($body, 'UTF-8');
        $minChars  = $sourceCharCount > 15000 ? 3500 : 2800;

        if ($bodyChars >= $minChars) {
            return null;
        }

        return "body is too thin for a {$sourceCharCount}-char source ({$bodyChars} chars) — "
            . 'add DECISION tables, STATE lifecycles, and checklist rules from the source';
    }

    private function looksLikeCodeDump(string $body): bool
    {
        $trim = ltrim($body);
        if (preg_match('/\A(json|xml|php|css|javascript)\s*[\r\n]+\s*[\{\<]/i', $trim) === 1) {
            return true;
        }
        if (str_starts_with($trim, '{') && str_contains($trim, '"$schema"')) {
            return true;
        }

        $lines    = preg_split('/\r\n|\r|\n/', $body) ?: [];
        $nonEmpty = array_values(array_filter($lines, static fn (string $line): bool => trim($line) !== ''));
        if ($nonEmpty === []) {
            return true;
        }

        $codeLike = 0;
        foreach ($nonEmpty as $line) {
            $t = trim($line);
            if (
                str_starts_with($t, '{')
                || str_starts_with($t, '}')
                || str_starts_with($t, '"')
                || str_starts_with($t, 'json')
                || preg_match('/^\s*[\{\}\[\]]/', $t) === 1
            ) {
                ++$codeLike;
            }
        }

        return ($codeLike / count($nonEmpty)) >= 0.35;
    }
}
