<?php

/**
 * LolaCore - PlaybookDistillTemplate
 *
 * Fill-in template and field contract for the Distiller.
 * Limits and required fields align with PlaybookValidator / playbook-format.
 *
 * @package LolaCore
 * @license GPL-2.0-or-later
 */

declare(strict_types=1);

namespace LolaCore\Playbooks;

/**
 * Single source for the distiller output shape (template-as-contract).
 */
final class PlaybookDistillTemplate
{
    /** @var array<int,string> */
    public const REQUIRED_BODY_SECTIONS = [
        'Purpose',
        'Key concepts',
        'Rules',
    ];

    /** @var array<int,string> */
    public const OPTIONAL_BODY_SECTIONS = [
        'Examples',
    ];

    /** @var array<int,string> */
    public const REQUIRED_FRONTMATTER_FIELDS = [
        'name',
        'description',
        'domain',
        'type',
    ];

    /**
     * Literal markdown skeleton the LLM must fill in (structure is fixed).
     */
    public static function markdownSkeleton(): string
    {
        $descMax = PlaybookValidator::DESCRIPTION_MAX_LENGTH;

        return "---\n"
            . "name: <kebab-case-slug>\n"
            . "description: <one specific sentence, max {$descMax} characters>\n"
            . "domain: <focused-domain-slug>\n"
            . "type: project\n"
            . "---\n\n"
            . "## Purpose\n"
            . "<domain boundary: what this playbook governs; when it applies (user_intent / domain signals)>\n\n"
            . "## Key concepts\n"
            . "- <term_or_constant>: <definition, mapping, or routing rule>\n"
            . "- <term_or_constant>: <definition, mapping, or routing rule>\n"
            . "- DECISION (if applicable): <when source compares alternatives — which to use when>\n"
            . "- ROUTING (if applicable): <intent signal → action/path table>\n"
            . "- STATE (if applicable): <ASCII lifecycle or state machine>\n\n"
            . "## Rules\n"
            . "1. <MUST/SHOULD constraint — executable, positive framing>\n"
            . "2. <MUST/SHOULD constraint — executable, positive framing>\n\n"
            . "## Examples\n"
            . "- <SPEC_TEMPLATE: minimal copyable pattern; delete section if nothing useful>";
    }

    /**
     * Filled-template few-shot: LLM-to-LLM style only (not copied into output).
     */
    public static function referenceOutputExample(): string
    {
        return <<<'MD'
---
name: brand-voice-acme
description: Tone, vocabulary, and formatting rules for Acme Corp customer-facing marketing copy.
domain: brand
type: project
---

## Purpose

`brand` domain — apply when `user_intent` involves customer-facing copy (landing pages, emails, ads, social). Out of scope: legal/HR internal docs, technical API reference.

## Key concepts

- `voice`: direct, warm, second person; no corporate jargon or hype adjectives.
- `forbidden_terms`: leverage, synergy, cutting-edge, game-changer.
- DECISION:
  | user need | use |
  |---|---|
  | headline / hero | short sentence, benefit-first, max 12 words |
  | body paragraph | one idea per paragraph; active voice |
  | CTA | verb + outcome ("Start your trial") — never "Click here" |
- ROUTING:
  | intent signal | target |
  |---|---|
  | product feature copy | benefits before specs |
  | error / apology email | acknowledge → fix → next step |

## Rules

1. MUST use sentence case for headings unless source brand guide specifies otherwise.
2. MUST spell product names exactly: Acme Flow, Acme Sync (never "acme flow").
3. SHOULD prefer numerals for stats ("47% faster") over spelled-out numbers in marketing copy.
4. MUST NOT invent claims, metrics, or testimonials absent from approved source material.

## Examples

- SPEC_TEMPLATE: feature bullet → "[Benefit]. [One proof point or constraint]."
MD;
    }

    /**
     * Target body size hint based on extracted source length.
     */
    public static function bodyTargetForSource(int $sourceCharCount): string
    {
        $max = PlaybookValidator::BODY_MAX_CHARS;

        if ($sourceCharCount > 15000) {
            $target = (int) round($max * 0.85);

            return "Source is large ({$sourceCharCount} chars). Aim for ~{$target}–{$max} chars in the body — extract decision tables, lifecycles, and checklist rules; do not stop at a thin summary.";
        }
        if ($sourceCharCount > 8000) {
            $target = (int) round($max * 0.65);

            return "Source is medium ({$sourceCharCount} chars). Aim for ~{$target}–{$max} chars in the body.";
        }

        return "Source is compact ({$sourceCharCount} chars). Stay within the body max; density over length.";
    }

    /**
     * Extraction priorities for large technical manuals (domain-agnostic).
     */
    public static function extractionPriorities(): string
    {
        return "When the source is long or technical, extract in this order:\n"
            . "1. DECISION / comparison sections (\"when to use X vs Y\") → `DECISION` table under Key concepts\n"
            . "2. Lifecycles and sync mechanics (draft/publish, file vs database) → `STATE` diagram\n"
            . "3. Terminology map and layer relationships\n"
            . "4. ROUTING (intent → path, file, tool, or screen)\n"
            . "5. Checklist / best-practice items → numbered MUST/SHOULD Rules\n"
            . "6. At most 1–2 SPEC_TEMPLATES under Examples\n"
            . "Never skip a decision or comparison table from the source — distill it, do not omit it.";
    }

    /**
     * System prompt: template-first contract, short limits reference.
     */
    public static function buildSystemPrompt(string $formatManual): string
    {
        $skeleton  = self::markdownSkeleton();
        $reference = self::referenceOutputExample();
        $limits    = self::limitsSummary();
        $sections = implode(', ', array_map(static fn (string $s): string => '## ' . $s, self::REQUIRED_BODY_SECTIONS));

        return "You fill in a fixed playbook TEMPLATE for LolaCore. "
            . "Your entire response IS the filled template file — nothing else.\n\n"
            . "## Output contract (non-negotiable)\n\n"
            . "1. First line of your response MUST be `---` (YAML frontmatter opening).\n"
            . "2. Copy the TEMPLATE structure exactly — same frontmatter keys, same `##` section headings in the same order.\n"
            . "3. Replace every `<placeholder>` with synthesized content from the source document.\n"
            . "4. Return ONLY the filled playbook. No preamble (\"Here is…\"), no explanation, no markdown code fences around the file.\n"
            . "5. Frontmatter keys only: `name`, `description`, `domain`, `type`. Quote `description` with double quotes.\n\n"
            . "## Template (copy this structure)\n\n"
            . $skeleton . "\n\n"
            . "## Audience\n\n"
            . "The body is injected into another LLM agent at execution time — write LLM-to-LLM operational reference "
            . "(domain boundaries, DECISION/ROUTING/STATE blocks, MUST/SHOULD rules), not a human tutorial.\n\n"
            . "## Distillation depth\n\n"
            . self::extractionPriorities() . "\n\n"
            . "## Frontmatter contract\n\n"
            . "| Field | Rule |\n"
            . "|---|---|\n"
            . "| `name` | kebab-case slug for the topic (e.g. `wordpress-fse`). Never `json`, `code`, `document`, or a file format. |\n"
            . "| `description` | One specific sentence; max {$limits['description_max']} chars. No placeholders like \"Distilled from uploaded document\". |\n"
            . "| `domain` | Focused slug: `fse`, `woocommerce`, `acf`, `brand`, etc. Never `general`. |\n"
            . "| `type` | Always `project` for uploaded documents. |\n\n"
            . "**YAML header rules (strict):** frontmatter uses only `name`, `description`, `domain`, `type`. "
            . "No nested keys, lists, or block scalars in the header. "
            . "Always wrap `description` in double quotes (e.g. `description: \"One sentence summary.\"`). "
            . "Tables, ROUTING, DECISION, and STATE blocks belong in the markdown body — never in frontmatter.\n\n"
            . "## Body contract\n\n"
            . "Required sections: {$sections}\n"
            . "- Synthesize the source into structured agent context — not narrative for end users.\n"
            . "- Body budget: max ~{$limits['body_max']} characters (~{$limits['body_tokens']} tokens). Large sources should use most of this budget.\n"
            . "- `## Purpose`: domain boundary and activation signals, not a marketing summary.\n"
            . "- `## Key concepts`: terminology maps, DECISION tables, ROUTING tables, STATE lifecycles — prefer structured blocks over paragraphs.\n"
            . "- `## Rules`: numbered MUST/SHOULD constraints the downstream agent enforces at runtime.\n"
            . "- `## Examples`: optional SPEC_TEMPLATES only; at most 1-2 compact patterns. Never paste theme.json, JSON configs, or large code blocks.\n"
            . "- If the source contains code/JSON, distill rules into Key concepts and Rules; keep at most a minimal SPEC_TEMPLATE under Examples.\n\n"
            . "## Reference output (style only)\n\n"
            . "The example below uses the `brand` domain to demonstrate density and structure only. "
            . "Your output topic, terms, DECISION tables, and rules must come entirely from the SOURCE document — "
            . "never reuse the reference topic or substitute its content.\n\n"
            . $reference . "\n\n"
            . "## Forbidden (instant rejection)\n\n"
            . "- Changing, removing, or reordering template sections\n"
            . "- Pasting JSON/XML/PHP/CSS as the main body content\n"
            . "- Human-facing tutorial or documentation tone (\"In this guide we will…\", \"As you can see…\")\n"
            . "- Leaving `<placeholder>` markers in the output\n"
            . "- Generic name/description/domain (see table above)\n\n"
            . "## Validation reference (PlaybookStore enforces this)\n\n"
            . $limits['text'] . "\n\n"
            . "## Full format manual (reference)\n\n"
            . $formatManual;
    }

    /**
     * User prompt: source document + explicit fill instruction.
     */
    public static function buildUserPrompt(
        string $sourceText,
        string $regenerateInstructions = ''
    ): string {
        $skeleton  = self::markdownSkeleton();
        $charCount = mb_strlen($sourceText);
        $extra     = trim($regenerateInstructions) !== ''
            ? "\n\nReviewer instructions:\n" . trim($regenerateInstructions)
            : '';
        $bodyTarget = self::bodyTargetForSource($charCount);

        return "Fill the playbook TEMPLATE below using the source document.\n\n"
            . "OUTPUT: Return ONLY the filled template file. First line MUST be `---`. No other text before or after.\n\n"
            . "Rules:\n"
            . "- Keep the exact template structure (frontmatter keys + section headings).\n"
            . "- Replace every `<...>` placeholder with real content synthesized from the source.\n"
            . "- Write for LLM consumption: operational, dense, structured — not a human-readable manual.\n"
            . "- Distill source comparison/decision tables into compact DECISION or ROUTING blocks — never skip them.\n"
            . "- Do not paste raw code blocks or full JSON configs; extract the rules they imply.\n"
            . "- Write in the same language as the source unless reviewer instructions say otherwise.\n\n"
            . "Source length: {$charCount} characters. {$bodyTarget}\n\n"
            . "--- TEMPLATE START ---\n"
            . $skeleton . "\n"
            . "--- TEMPLATE END ---\n\n"
            . "--- SOURCE DOCUMENT START ---\n"
            . $sourceText
            . "\n--- SOURCE DOCUMENT END ---"
            . $extra;
    }

    public static function buildFormatCorrectionUserPrompt(
        string $sourceText,
        string $rejectedRaw,
        string $regenerateInstructions = ''
    ): string {
        $snippet = mb_substr(trim($rejectedRaw), 0, 600);
        $skeleton = self::markdownSkeleton();

        return self::buildUserPrompt($sourceText, $regenerateInstructions)
            . "\n\n## FORMAT CORRECTION REQUIRED\n"
            . "Your previous response was NOT a filled playbook file (missing YAML frontmatter starting with `---`).\n"
            . "Rejected output (excerpt — do NOT repeat this shape):\n"
            . $snippet
            . "\n\nReturn ONLY the filled TEMPLATE below. First line MUST be exactly `---`.\n"
            . "No introduction, no explanation, no JSON, no code fences around the file.\n\n"
            . "--- TEMPLATE START ---\n"
            . $skeleton . "\n"
            . "--- TEMPLATE END ---";
    }

    /**
     * Correction pass when quality gate rejects the first draft.
     */
    public static function buildCorrectionUserPrompt(
        string $sourceText,
        string $rejectedDraft,
        string $issue,
        string $regenerateInstructions = ''
    ): string {
        $snippet = mb_substr($rejectedDraft, 0, 600);

        return self::buildUserPrompt($sourceText, $regenerateInstructions)
            . "\n\n## CORRECTION REQUIRED\n"
            . "Your previous filled template was rejected: {$issue}\n"
            . "Rejected output (excerpt — do NOT repeat this):\n"
            . $snippet
            . "\n\nFill the TEMPLATE again from scratch with valid name, description, domain, and LLM-oriented operational content in every required section.";
    }

    /**
     * @return array{description_max:int,body_max:int,body_tokens:int,text:string}
     */
    public static function limitsSummary(): array
    {
        $descMax = PlaybookValidator::DESCRIPTION_MAX_LENGTH;
        $bodyMax = PlaybookValidator::BODY_MAX_CHARS;
        $bodyTok = PlaybookValidator::BODY_MAX_TOKENS;

        return [
            'description_max' => $descMax,
            'body_max'        => $bodyMax,
            'body_tokens'     => $bodyTok,
            'text'            => "description ≤ {$descMax} chars; body ≤ {$bodyMax} chars (~{$bodyTok} tokens); "
                . 'type must be `system` or `project` (use `project` for uploads).',
        ];
    }
}
