<?php

namespace App\DTOs;

/**
 * What a landing page says about itself.
 *
 * Deliberately platform-neutral. This is the first thing the chat learns about
 * a campaign, before anyone has chosen Meta or Google, and everything it
 * produces is a suggestion rather than a decision: the page is evidence, not an
 * instruction.
 */
readonly class LandingPageAnalysis
{
    /**
     * @param  list<string>  $headings
     * @param  list<string>  $keywords  most frequent meaningful words, longest first
     */
    public function __construct(
        public string $url,
        public string $title,
        public string $description,
        public array $headings,
        public string $text,
        public array $keywords,
    ) {}

    /** The advertiser's name as the page presents it, best effort. */
    public function businessName(): string
    {
        return $this->headings[0] ?? $this->title;
    }

    /**
     * A campaign name a person would recognise.
     *
     * No platform in the name: the same brief can produce a campaign on several
     * platforms, and "Acme - Google Ads" would be wrong on half of them.
     */
    public function campaignName(): string
    {
        return $this->businessName() !== '' ? $this->businessName() : 'New campaign';
    }

    /** @return list<string> candidate headlines, longest-standing first */
    public function headlines(): array
    {
        return array_values(array_unique(array_filter([$this->title, ...$this->headings])));
    }

    /** @return list<string> candidate descriptions */
    public function descriptions(): array
    {
        return array_values(array_filter([
            $this->description,
            $this->text !== '' ? mb_substr($this->text, 0, 90) : null,
        ]));
    }

    /**
     * What the page looks like it is for, in plain words.
     *
     * A shop page wants sales, a form wants leads, an article wants traffic.
     * Returned as the brief's vocabulary rather than any platform's, so the
     * same answer maps onto Meta's OUTCOME_SALES and Google's conversion goal.
     *
     * A guess, and treated as one everywhere it is used: it is offered as a
     * suggestion with its reason shown, never written as though the user said
     * it. Null when the page gives no clear signal, because a wrong suggestion
     * is worse than none.
     *
     * @return array{outcome: string, because: string}|null
     */
    public function suggestedOutcome(): ?array
    {
        $haystack = mb_strtolower($this->title.' '.$this->description.' '.implode(' ', $this->headings).' '.$this->text);

        // Ordered by how strongly each signal implies intent. A page selling
        // something often also has a newsletter form, so the checkout wins.
        $signals = [
            'sales' => ['add to cart', 'add to basket', 'buy now', 'checkout', 'free shipping', 'in stock', 'order now'],
            'leads' => ['get a quote', 'request a demo', 'book a call', 'contact us', 'sign up', 'free trial', 'subscribe'],
        ];

        foreach ($signals as $outcome => $phrases) {
            foreach ($phrases as $phrase) {
                if (str_contains($haystack, $phrase)) {
                    return ['outcome' => $outcome, 'because' => 'the page says "'.$phrase.'"'];
                }
            }
        }

        return null;
    }

    /** @return array<string, mixed> */
    public function toArray(): array
    {
        return [
            'url' => $this->url,
            'page' => [
                'title' => $this->title,
                'description' => $this->description,
                'headings' => $this->headings,
                'text' => $this->text,
            ],
            'suggestions' => [
                'campaign_name' => $this->campaignName(),
                'business_name' => $this->businessName(),
                'headlines' => $this->headlines(),
                'descriptions' => $this->descriptions(),
                'keywords' => implode(', ', $this->keywords),
            ],
        ];
    }
}
