<?php

declare(strict_types=1);

final class MyronJsonLdGenerator
{
    public function __construct(private readonly MyronSeoFieldResolver $resolver)
    {
    }

    /**
     * @param array<string, mixed> $settings
     * @return list<array<string, mixed>>
     */
    public function globalSchemas(array $settings): array
    {
        $siteUrl = rtrim(myronAppUrl() . myronPublicBasePath(), '/') . '/';
        $orgName = trim((string) (($settings['seo_org_name'] ?? '') ?: ($settings['site_title'] ?? myronSiteTitle())));
        $orgUrl = trim((string) (($settings['seo_org_url'] ?? '') ?: $siteUrl));
        $orgId = rtrim($orgUrl, '/') . '#organization';
        $sameAs = is_array($settings['seo_social_profiles'] ?? null) ? array_values(array_filter(array_map('strval', $settings['seo_social_profiles']))) : [];
        $organization = [
            '@context' => 'https://schema.org',
            '@type' => 'Organization',
            '@id' => $orgId,
            'name' => $orgName,
            'url' => $orgUrl,
        ];
        $logo = trim((string) ($settings['seo_org_logo'] ?? ''));
        if ($logo !== '') {
            $organization['logo'] = ['@type' => 'ImageObject', 'url' => $logo];
        }
        if ($sameAs !== []) {
            $organization['sameAs'] = $sameAs;
        }

        $website = [
            '@context' => 'https://schema.org',
            '@type' => 'WebSite',
            '@id' => $siteUrl . '#website',
            'name' => myronSiteTitle(),
            'url' => $siteUrl,
            'publisher' => ['@id' => $orgId],
            'potentialAction' => [
                '@type' => 'SearchAction',
                'target' => $siteUrl . '?s={search_term_string}',
                'query-input' => 'required name=search_term_string',
            ],
        ];

        $schemas = [$organization, $website];
        $localBusiness = $this->localBusinessSchema($settings, $orgName, $orgUrl);
        if ($localBusiness !== null) {
            $schemas[] = $localBusiness;
        }

        return $schemas;
    }

    /**
     * @param array<string, mixed> $page
     * @param array<string, mixed> $settings
     * @return list<array<string, mixed>|string>
     */
    public function pageSchemas(array $page, array $settings): array
    {
        $requestPath = trim((string) ($page['_requestPath'] ?? ''));
        if ($requestPath !== '') {
            $page['path'] = myronNormalizePath($requestPath);
        }
        $mode = trim((string) ($page['jsonld_mode'] ?? 'auto')) ?: 'auto';
        if ($mode === 'none') {
            return [];
        }

        $schemas = [];
        if ($mode === 'custom') {
            $custom = trim((string) ($page['jsonld_custom'] ?? ''));
            if ($custom === '') {
                return [$this->breadcrumbSchema($page)];
            }
            try {
                $decoded = json_decode($custom, true, 512, JSON_THROW_ON_ERROR);
                if (is_array($decoded)) {
                    $schemas[] = $decoded;
                }
            } catch (Throwable) {
                return ['<!-- Invalid Custom JSON-LD -->'];
            }
        } else {
            $schemas[] = $this->articleSchema($page, $settings);
        }

        $schemas[] = $this->breadcrumbSchema($page);
        return $schemas;
    }

    /**
     * @param array<string, mixed> $page
     * @param array<string, mixed> $settings
     * @return array<string, mixed>
     */
    private function articleSchema(array $page, array $settings): array
    {
        $resolved = $this->resolver->resolveAll($page);
        $canonical = (string) ($resolved['canonical']['value'] ?? '');
        $content = is_array($page['content'] ?? null) ? (array) $page['content'] : [];
        $body = $this->bodyText($content);
        $type = trim((string) ($page['jsonld_type'] ?? ''));
        if ($type === '') {
            $siteType = strtolower(trim((string) (($page['source_bundle_id'] ?? '') ?: ($page['source_template_id'] ?? '') ?: ($page['template_key'] ?? ''))));
            $type = str_contains($siteType, 'blog') || str_contains($siteType, 'news') ? 'BlogPosting' : 'Article';
        }
        if (!in_array($type, ['Article', 'BlogPosting', 'WebPage'], true)) {
            $type = 'Article';
        }

        $schema = [
            '@context' => 'https://schema.org',
            '@type' => $type,
            'mainEntityOfPage' => ['@type' => 'WebPage', '@id' => $canonical],
            'headline' => (string) ($resolved['seo_title']['value'] ?? ''),
            'description' => (string) ($resolved['meta_description']['value'] ?? ''),
            'url' => $canonical,
            'datePublished' => (string) ($page['published_at'] ?? ''),
            'dateModified' => (string) (($page['updated_at'] ?? '') ?: ($page['published_at'] ?? '')),
            'articleBody' => $body,
            'wordCount' => str_word_count($body),
            'author' => $this->personSchema($settings),
            'publisher' => ['@id' => rtrim((string) (($settings['seo_org_url'] ?? '') ?: myronAppUrl()), '/') . '#organization'],
        ];

        $image = trim((string) ($resolved['og_image']['value'] ?? ''));
        if ($image !== '') {
            $schema['image'] = ['@type' => 'ImageObject', 'url' => $image];
        }

        return $schema;
    }

    /**
     * @param array<string, mixed> $settings
     * @return array<string, mixed>
     */
    public function personSchema(array $settings = []): array
    {
        $user = function_exists('myronCurrentUser') ? myronCurrentUser() : null;
        $name = is_array($user) ? trim((string) ($user['display_name'] ?? '')) : '';
        $person = [
            '@type' => 'Person',
            'name' => $name !== '' ? $name : myronSiteTitle(),
            'url' => myronAppUrl() . myronPublicBasePath(),
        ];

        if (is_array($user)) {
            $jobTitle = trim((string) ($user['job_title'] ?? ''));
            if ($jobTitle !== '') {
                $person['jobTitle'] = $jobTitle;
            }
            $sameAs = $this->sameAsFromUser($user);
            if ($sameAs !== []) {
                $person['sameAs'] = $sameAs;
            }
        }

        return $person;
    }

    /**
     * @param array<string, mixed> $page
     * @return array<string, mixed>
     */
    private function breadcrumbSchema(array $page): array
    {
        $path = myronNormalizePath((string) ($page['path'] ?? '/'));
        $canonical = myronAppUrl() . myronBuildPublicPageUrl($path);
        $items = [
            ['@type' => 'ListItem', 'position' => 1, 'name' => 'Home', 'item' => myronAppUrl() . myronBuildPublicPageUrl('/')],
        ];
        if ($path !== '/') {
            $items[] = ['@type' => 'ListItem', 'position' => 2, 'name' => (string) (($page['version_title'] ?? '') ?: ($page['title'] ?? 'Page')), 'item' => $canonical];
        }
        return ['@context' => 'https://schema.org', '@type' => 'BreadcrumbList', 'itemListElement' => $items];
    }

    /**
     * @param array<string, mixed> $settings
     * @return array<string, mixed>|null
     */
    private function localBusinessSchema(array $settings, string $orgName, string $orgUrl): ?array
    {
        if (empty($settings['seo_local_business_enabled'])) {
            return null;
        }
        $name = trim((string) ($settings['seo_local_business_name'] ?? '')) ?: $orgName;
        $schema = [
            '@context' => 'https://schema.org',
            '@type' => 'LocalBusiness',
            '@id' => rtrim($orgUrl, '/') . '#localbusiness',
            'name' => $name,
            'url' => $orgUrl,
        ];
        foreach ([
            'telephone' => 'seo_local_business_telephone',
            'openingHours' => 'seo_local_business_hours',
            'image' => 'seo_local_business_image',
        ] as $schemaKey => $settingsKey) {
            $value = trim((string) ($settings[$settingsKey] ?? ''));
            if ($value !== '') {
                $schema[$schemaKey] = $value;
            }
        }
        $address = trim((string) ($settings['seo_local_business_address'] ?? ''));
        if ($address !== '') {
            $schema['address'] = ['@type' => 'PostalAddress', 'streetAddress' => $address];
        }
        return $schema;
    }

    /**
     * @param array<string, mixed> $content
     */
    private function bodyText(array $content): string
    {
        $parts = [];
        array_walk_recursive($content, static function (mixed $value) use (&$parts): void {
            if (is_scalar($value)) {
                $parts[] = trim(strip_tags((string) $value));
            }
        });
        return trim(preg_replace('/\s+/', ' ', implode(' ', array_filter($parts))) ?? '');
    }

    /**
     * @param array<string, mixed> $user
     * @return list<string>
     */
    private function sameAsFromUser(array $user): array
    {
        $sameAs = [];
        $json = json_decode((string) ($user['same_as_urls_json'] ?? '[]'), true);
        if (is_array($json)) {
            $sameAs = array_merge($sameAs, array_map('strval', $json));
        }
        foreach (['facebook_url', 'x_url', 'linkedin_url', 'youtube_url', 'instagram_url'] as $field) {
            $value = trim((string) ($user[$field] ?? ''));
            if ($value !== '') {
                $sameAs[] = $value;
            }
        }
        return array_values(array_unique(array_filter($sameAs)));
    }
}
