<?php

namespace App\Services\AI;

use App\Contracts\PromptLandingPageHtmlGenerator;
use App\DTOs\GeneratedLandingPageHtml;
use App\DTOs\PromptLandingPageBrief;
use App\Models\PromptLandingPage;
use App\Support\CssFontFamily;
use Illuminate\Contracts\JsonSchema\JsonSchema;

use function Laravel\Ai\agent;

class LaravelAiPromptLandingPageHtmlGenerator implements PromptLandingPageHtmlGenerator
{
    /**
     * @param  list<string>  $forbiddenTokens
     */
    public function generate(
        PromptLandingPageBrief $brief,
        ?string $previousHtml = null,
        ?string $revisionNotes = null,
        array $forbiddenTokens = [],
    ): GeneratedLandingPageHtml {
        $prompt = $this->buildPrompt($brief, $previousHtml, $revisionNotes, $forbiddenTokens);

        $response = agent(
            // instructions: 'You write a single conversion-focused promotional landing page from a marketing brief. The page must be fully responsive: it has to look correct on phones, tablets, and desktops. When the brief lists hex colors, the accent color is the theme: use it for buttons, links, header highlights, and CTAs. Use background and text colors for the page/body. Primary and secondary are supporting only. Do not invent a conflicting palette. If a logo is present, redraw it as a simple inline SVG from the logo description — never embed or hotlink uploaded files. Return HTML markup, a complete stylesheet, and JavaScript as separate fields. Do not use external CSS frameworks or markdown. Never embed, hotlink, or reference uploaded user files, local disk paths, storage URLs, or original attachment filenames. Other visuals must be CSS, inline SVG, or generic public placeholder images such as https://placehold.co. Do not invent product catalog URLs that were not in the brief.',
            instructions: 'You write a single conversion-focused promotional landing page from a marketing brief. The page must be fully responsive: it has to look correct on phones, tablets, and desktops. The accent color is the theme: use that exact hex for filled buttons, links, header highlights, and CTAs. Use background and text colors for the page/body. Do not invent a dark palette when the background color is light. Primary and secondary are supporting only. Do not invent a conflicting palette. If an exact logo SVG is provided in the brief, embed that markup verbatim as the header logo — do not redraw, simplify, restyle, or alter it in any way, just place it in the markup and size it with CSS. If no exact SVG is provided but a public logo URL is, use that URL as the header <img> src — it is the live website\'s public logo or favicon, not an upload. Only redraw a logo as inline SVG from the logo description when neither an exact SVG nor a public logo URL was provided. Never embed uploaded files, local disk paths, storage URLs, or raster data URIs. Return HTML markup, a complete stylesheet, and JavaScript as separate fields. Do not use external CSS frameworks or markdown. Other visuals must be CSS, inline SVG, or generic public placeholder images such as https://placehold.co. Do not invent product catalog URLs that were not in the brief.',
            schema: fn (JsonSchema $schema) => [
                'title' => $schema->string()->description('The HTML document title.')->required(),
                'html' => $schema->string()->description('Page markup only. No style or script tags, no doctype or html/head wrappers.')->required(),
                'css' => $schema->string()->description('A complete, non-empty stylesheet for every class and id in the html field. No style tags.')->required(),
                'js' => $schema->string()->description('JavaScript for interactive controls. No script tags. Empty string if none.')->required(),
            ],
        )->prompt($prompt, provider: TextProviderFailover::providers(), timeout: 1200);

        $data = $response->toArray();

        $html = $this->normalizeSnippet((string) ($data['html'] ?? ''), 'html');
        $css = $this->normalizeSnippet((string) ($data['css'] ?? ''), 'css');
        $js = $this->normalizeSnippet((string) ($data['js'] ?? ''), 'js');

        [$html, $css, $js] = $this->extractEmbeddedAssets($html, $css, $js);
        $css = $this->enforceThemeCss($css, $brief);
        $css = $this->enforceFontCss($css, $brief);

        return new GeneratedLandingPageHtml(
            title: $data['title'] ?? $brief->offering,
            html: $html,
            css: $css,
            js: $js,
            prompt: $prompt,
        );
    }

    /**
     * @param  list<string>  $forbiddenTokens
     */
    private function buildPrompt(
        PromptLandingPageBrief $brief,
        ?string $previousHtml,
        ?string $revisionNotes,
        array $forbiddenTokens,
    ): string {
        $briefBlock = sprintf(
            "Brand: %s\nOffering: %s\nPurpose: %s\nHeadline: %s\nAudience: %s\nTone: %s\nPricing: %s\nCTA: %s\nFeatures: %s\nBenefits: %s\nUSPs: %s\nPain points: %s\nTheme / accent color: %s\nPrimary color (supporting): %s\nSecondary color (supporting): %s\nBackground color: %s\nText color: %s\nFont family: %s\nFont stylesheet URL: %s\nLogo present: %s\nLogo type: %s\nLogo wordmark text: %s\nLogo description: %s\nLogo URL: %s\nFavicon URL: %s\nNotes: %s",
            $brief->brandName,
            $brief->offering,
            $brief->purpose !== '' ? $brief->purpose : 'none',
            $brief->headline,
            $brief->audience,
            $brief->tone,
            $brief->pricing,
            $brief->cta,
            implode(', ', $brief->features),
            implode(', ', $brief->benefits),
            implode(', ', $brief->usps),
            implode(', ', $brief->painPoints),
            $brief->accentColor,
            $brief->primaryColor,
            $brief->secondaryColor,
            $brief->backgroundColor,
            $brief->textColor,
            $brief->fontFamily !== '' ? $brief->fontFamily : 'none',
            $brief->fontStylesheetUrl !== '' ? $brief->fontStylesheetUrl : 'none',
            $brief->logoPresent ? 'yes' : 'no',
            $brief->logoType,
            $brief->logoWordmarkText !== '' ? $brief->logoWordmarkText : 'none',
            $brief->logoDescription !== '' ? $brief->logoDescription : 'none',
            $brief->logoUrl !== '' ? $brief->logoUrl : 'none',
            $brief->faviconUrl !== '' ? $brief->faviconUrl : 'none',
            $brief->notes ?? 'none',
        );

        if ($brief->logoExactSvg !== '') {
            $briefBlock .= "\n\nEXACT logo SVG (embed this verbatim in the header, unmodified, sized with CSS only):\n".$brief->logoExactSvg;
        }

        $forbidden = $forbiddenTokens === []
            ? 'none'
            : implode(', ', $forbiddenTokens);

        $website = $brief->brandName !== '' ? $brief->brandName : 'the website';

        $rules = $this->sharedPageRules()."\n".$this->conversionRules($brief, $website);

        if (filled($previousHtml) && filled($revisionNotes)) {
            return <<<PROMPT
{$briefBlock}

Forbidden tokens (must not appear in html, css, or js): {$forbidden}

The user asked to revise the existing landing page:
{$revisionNotes}

Keep the page as one HTML document. Apply the requested changes. Preserve what still works. The revised page must remain fully responsive on mobile, tablet, and desktop.

{$rules}

Current HTML:
{$previousHtml}
PROMPT;
        }

        return <<<PROMPT
{$briefBlock}

Forbidden tokens (must not appear in html, css, or js): {$forbidden}

Write one original landing page from this brief. There is no product URL catalog and no uploaded assets to embed.

{$rules}
PROMPT;
    }

    private function sharedPageRules(): string
    {
        return <<<'RULES'
Return three separate fields: html (markup only), css (stylesheet only), and js (javascript only). Do not include <style> or <script> tags, and do not wrap the html field in doctype/html/head.
Write a complete, polished, conversion-ready single landing page: header, hero, supporting sections, and a clear CTA. The css field must not be empty.
The layout must be mobile-first and fully responsive. Use fluid widths, not fixed page widths such as 1440px. Images, videos, and iframes must use max-width: 100% and height: auto. Nav, hero, and multi-column sections must stack on small screens (max-width: 767px), sit comfortably on tablets (768px–991px), and use the full desktop layout from 992px. Type should scale (rem or clamp). Primary buttons must be at least 44px tall for touch. Include @media queries for 767px and 991px. There must be no horizontal scrolling. Do not set html or body to height: 100% with overflow: hidden — the page must stay window-scrollable so in-page CTA hash links (#form) can return to the top. Use window scrolling, not a nested overflow container.
Never put uploaded files on the page. Do not use local paths, storage URLs, data:image raster URIs, or any of the forbidden tokens as src or href.
Use the exact hex colors given above as CSS custom properties. The Theme / accent color is the theme: set :root { --theme: <accent>; --accent: <accent>; --primary: <primary>; --secondary: <secondary>; --background: <background>; --text: <text>; }. Buttons, links, header highlights, and CTAs must use var(--theme) (the accent hex) as a solid fill — not a different blue, not an outline-only button. Background color and text color are for the page and body copy. Do not invent a dark page when background color is light. Primary and secondary are supporting only (borders, hover, section tints). Do not use primary as the main button or header color when an accent hex is provided. If accent is blank, fall back to primary. Do not substitute a different palette when hex colors are provided.
If Font family is not none, use that exact typeface for html, body, and page copy: set :root { --font: <stack>; } and html, body { font-family: var(--font); }. Do not invent a different font. If a Font stylesheet URL is given, put @import url("<url>"); as the first line of the css field so the font loads. Do not add a <link> for fonts in the html snippet.
If Logo present is yes and an EXACT logo SVG is given above, embed that SVG markup exactly as provided in the page header — do not redraw it, change its paths, or alter its colors. Only wrap it in a container and apply width/height via CSS. If Logo present is yes, no exact SVG is given, and a public Logo URL is given, use that URL as the header <img> src — it is the live website's public logo or favicon, not an upload. Do not replace it with a placeholder or a redrawn SVG. If Logo present is yes but no exact SVG and no public Logo URL is given, draw a simple inline SVG header logo from the logo type, wordmark text, and logo description, using the brand colors. Do not use an <img> of an uploaded file, a local disk path, a storage URL, or a raster data URI. If Logo present is no, use the brand name as text in the header and do not invent a logo. Do not add a <link rel="icon"> in the html snippet; the app injects the Favicon URL into the document head.
RULES;
    }

    private function conversionRules(PromptLandingPageBrief $brief, string $website): string
    {
        if ($brief->purpose === PromptLandingPage::PURPOSE_APP_DOWNLOAD) {
            return $this->appDownloadConversionRules();
        }

        return $this->leadPageConversionRules($website);
    }

    private function leadPageConversionRules(string $website): string
    {
        return <<<RULES
After the lead form is submitted, the only CTA must be "Return to {$website}" as a link to "/" — the website index. Do not add "Continue to secure signup" or any other continue, checkout, payment, or signup CTA. Do not invent a checkout, payment, or signup URL.
If the page includes a lead form, always add proper HTML validation even when the brief does not mention it: required on name and email (and on phone when that field exists), type="email" for email fields, type="tel" for phone fields, and short in-form error text. Do not allow an empty submit.
RULES;
    }

    private function appDownloadConversionRules(): string
    {
        return <<<'RULES'
This page purpose is App download. Do not require a lead form. Include a lead form only if the notes or user prompt ask for one. If a form is included, add proper HTML validation: required on name and email (and on phone when that field exists), type="email" for email fields, type="tel" for phone fields, and short in-form error text. Do not allow an empty submit.
The primary conversion is a download CTA. Use an <a> whose href is the exact http(s) app download URL from the notes or user prompt. Put data-admedia-download="1" on every download CTA (hero, sticky bar, and footer). Do not invent an App Store, Play Store, or APK URL. If no download URL was given, use href="#" and still mark the CTA with data-admedia-download="1".
Do not add a "Return to" website link. Do not add "Continue to secure signup" or any checkout, payment, or signup CTA.
RULES;
    }

    /**
     * @return array{0: string, 1: string, 2: string}
     */
    private function extractEmbeddedAssets(string $html, string $css, string $js): array
    {
        if (preg_match_all('/<style\b[^>]*>(.*?)<\/style>/is', $html, $styles) > 0) {
            $extracted = collect($styles[1])
                ->map(fn (string $block) => trim($block))
                ->filter()
                ->implode("\n");

            if ($extracted !== '') {
                $css = trim($css === '' ? $extracted : $css."\n".$extracted);
            }

            $html = (string) preg_replace('/<style\b[^>]*>.*?<\/style>/is', '', $html);
        }

        if (preg_match_all('/<script\b[^>]*>(.*?)<\/script>/is', $html, $scripts) > 0) {
            $extracted = collect($scripts[1])
                ->map(fn (string $block) => trim($block))
                ->filter()
                ->implode("\n");

            if ($extracted !== '') {
                $js = trim($js === '' ? $extracted : $js."\n".$extracted);
            }

            $html = (string) preg_replace('/<script\b[^>]*>.*?<\/script>/is', '', $html);
        }

        return [trim($html), $css, $js];
    }

    private function enforceThemeCss(string $css, PromptLandingPageBrief $brief): string
    {
        $theme = $brief->accentColor !== '' ? $brief->accentColor : $brief->primaryColor;

        if ($theme === '') {
            return $css;
        }

        $primary = $brief->primaryColor !== '' ? $brief->primaryColor : $theme;
        $secondary = $brief->secondaryColor !== '' ? $brief->secondaryColor : $theme;
        $background = $brief->backgroundColor !== '' ? $brief->backgroundColor : '#ffffff';
        $text = $brief->textColor !== '' ? $brief->textColor : '#111111';

        $block = <<<CSS
:root {
  --theme: {$theme};
  --accent: {$theme};
  --primary: {$primary};
  --secondary: {$secondary};
  --background: {$background};
  --text: {$text};
}
html, body {
  background-color: var(--background);
  color: var(--text);
}
.btn, .button, .cta, [class*="btn-"], [class*="cta"] {
  background-color: var(--theme);
  color: #fff;
  border-color: var(--theme);
}
CSS;

        return trim($css."\n".$block);
    }

    private function enforceFontCss(string $css, PromptLandingPageBrief $brief): string
    {
        if ($brief->fontFamily === '') {
            return $css;
        }

        $stack = CssFontFamily::cssValue($brief->fontFamily);
        $block = <<<CSS
:root {
  --font: {$stack};
}
html, body {
  font-family: var(--font);
}
CSS;

        $css = trim($css."\n".$block);
        $importUrl = $brief->fontStylesheetUrl !== ''
            ? $brief->fontStylesheetUrl
            : CssFontFamily::stylesheetUrl($brief->fontFamily);

        if ($importUrl === null || str_contains($css, $importUrl)) {
            return $css;
        }

        return '@import url("'.$importUrl.'");'."\n".$css;
    }

    private function normalizeSnippet(string $value, string $language): string
    {
        $value = trim($value);
        $value = (string) preg_replace('/^```(?:'.$language.')?\s*/i', '', $value);
        $value = (string) preg_replace('/```$/', '', $value);

        return trim($value);
    }
}
