import type { Section } from '@/marketing/cms-types';
import type { SharedProps } from '@/types/inertia';
import { Head, usePage } from '@inertiajs/react';

export type PageSeoOverride = {
    title?: string;
    description?: string;
    /** Storage path — admin uploaded via the SEO card. Server-resolved
     *  to a URL via `ogImageUrl`. */
    ogImage?: string;
    ogImageUrl?: string;
    noindex?: boolean;
    keywords?: string;
};

// Tracking params stripped from canonical URLs so that
// /pricing?utm_source=twitter and /pricing aren't seen as separate
// pages by Google. Conservative allowlist — anything outside this list
// stays in the URL (might be load-bearing app state).
const STRIP_PARAMS = [
    'utm_source',
    'utm_medium',
    'utm_campaign',
    'utm_term',
    'utm_content',
    'utm_id',
    'gclid',
    'fbclid',
    'mc_eid',
    'mc_cid',
    '_ga',
    'ref',
    'referrer',
    'igshid',
];

function buildCanonical(pathWithQuery: string, origin: string): string {
    const [path, query] = pathWithQuery.split('?');
    if (!query) return origin + path;
    const params = new URLSearchParams(query);
    STRIP_PARAMS.forEach((p) => params.delete(p));
    const cleaned = params.toString();
    return origin + path + (cleaned ? `?${cleaned}` : '');
}

/** Strip HTML tags + collapse whitespace for plain-text descriptions
 *  derived from CMS HTML body fields. Google + Bing prefer plain text
 *  for meta description; long blocks of HTML markup confuse them. */
function htmlToPlainText(html: string): string {
    return html
        .replace(/<[^>]+>/g, ' ')
        .replace(/&nbsp;/g, ' ')
        .replace(/&amp;/g, '&')
        .replace(/&quot;/g, '"')
        .replace(/&#39;/g, "'")
        .replace(/&lt;/g, '<')
        .replace(/&gt;/g, '>')
        .replace(/\s+/g, ' ')
        .trim();
}

/** Build FAQPage JSON-LD from every `faq` section on the page. Multiple
 *  FAQ sections concatenate into one schema (Google recommends one
 *  FAQPage per URL). Returns null when no FAQ items exist. */
function buildFaqJsonLd(sections: Section[]): string | null {
    const items: { q: string; a: string }[] = [];
    for (const section of sections) {
        if (section.type !== 'faq' || section.enabled === false) continue;
        for (const item of section.items ?? []) {
            const q = htmlToPlainText(item.q ?? '');
            if (!q) continue;
            items.push({ q, a: item.a ?? '' });
        }
    }
    if (items.length === 0) return null;
    return JSON.stringify({
        '@context': 'https://schema.org',
        '@type': 'FAQPage',
        mainEntity: items.map((item) => ({
            '@type': 'Question',
            name: item.q,
            // FAQPage spec accepts HTML in answer.text; keep markup so
            // links + bold + lists render in the SERP rich snippet.
            acceptedAnswer: { '@type': 'Answer', text: item.a },
        })),
    });
}

/** Last-resort description when admin hasn't set a per-page or site-wide
 *  description. Walks sections looking for the first meaningful body
 *  copy — features subhead, FAQ support text, hero subhead. Truncates
 *  at Google's snippet limit (~160 chars) so we don't ship a wall of
 *  text into the meta. Returns empty string when nothing suitable. */
function deriveFallbackDescription(sections: Section[]): string {
    for (const section of sections) {
        if (section.enabled === false) continue;
        const candidates: (string | undefined)[] = [];
        if ('subhead' in section) candidates.push(section.subhead);
        if ('supportText' in section) candidates.push(section.supportText);
        for (const html of candidates) {
            if (!html) continue;
            const text = htmlToPlainText(html);
            if (text.length < 30) continue; // too short to be useful
            return text.length > 160 ? text.slice(0, 157) + '…' : text;
        }
    }
    return '';
}

type Props = {
    /** Display name for the `{page}` template variable when no explicit
     *  meta title is set. e.g. "Pricing", "About". */
    pageLabel: string;
    /** Per-page override fields. Optional — falls through to site-wide
     *  defaults from shared `marketing.seo`. */
    override?: PageSeoOverride;
    /** Page sections — used to auto-emit FAQPage JSON-LD for any `faq`
     *  sections and to derive a default description when admin hasn't
     *  set one in the SEO card. Optional; if not passed, no schema is
     *  emitted and the description chain falls back to site default. */
    sections?: Section[];
    /** Extra JSON-LD blocks (Product, BreadcrumbList, etc) the page wants
     *  emitted alongside the auto-generated Organization + FAQPage.
     *  Passed pre-serialized so callers can build complex schemas
     *  server-side without runtime-reactivating them. */
    extraJsonLd?: string[];
};

/**
 * Emits SEO meta tags for a public marketing page. Resolves the fallback
 * chain from site-wide `marketing.seo` defaults + per-page overrides.
 *
 * Drop into the page's `<Head>` block:
 *   <Head title="Welcome">
 *     <PageSeo pageLabel="Welcome" override={config.seo} />
 *   </Head>
 *
 * The component owns all SEO `<meta>`s — page only needs the visible
 * `<title>`. We render our own `<title>` here too, but Inertia keeps the
 * last one — putting <PageSeo> AFTER any literal title tag means our
 * resolved value wins.
 */
export default function PageSeo({ pageLabel, override, sections, extraJsonLd }: Props) {
    const props = usePage<SharedProps>().props;
    const siteSeo = props.marketing?.seo;
    const branding = props.branding;
    const currentPath = (usePage().url ?? '/').split('?')[0] ?? '/';

    const siteName = branding?.site_name || 'AppShot AI';

    // Title resolution: page-override > template-filled > raw page label
    let title = override?.title?.trim() || '';
    if (!title) {
        const template = siteSeo?.titleTemplate || '{page} — {site}';
        title = template.replace('{page}', pageLabel).replace('{site}', siteName);
    }

    // Description chain: page-override > site default > sections-derived
    // > empty (omit tag). The sections-derived fallback walks the page
    // sections looking for the first decent body copy (features subhead,
    // FAQ support text, hero subhead) — better-than-nothing fallback when
    // admin skips the optional description.
    const description =
        override?.description?.trim() ||
        siteSeo?.description?.trim() ||
        (sections ? deriveFallbackDescription(sections) : '') ||
        '';

    // OG image fallback chain: page override > site SEO default >
    // branding logo. (Appearance og_image was retired — SEO panel owns
    // social-share images, Appearance owns brand chrome.)
    const ogImage =
        override?.ogImageUrl ||
        siteSeo?.ogImageUrl ||
        branding?.logo_url ||
        null;

    const twitterHandle = siteSeo?.twitterHandle?.trim() || '';
    // Per-page override OR the site-wide "discourage search engines" switch.
    const noindex =
        override?.noindex === true || siteSeo?.discourageIndexing === true;
    const keywords = override?.keywords?.trim() || '';

    const origin =
        typeof window !== 'undefined' && window.location?.origin
            ? window.location.origin
            : '';
    const fullPathWithQuery =
        typeof window !== 'undefined' && window.location?.search
            ? currentPath + window.location.search
            : currentPath;
    const canonicalUrl = buildCanonical(fullPathWithQuery, origin);
    const ogUrl = canonicalUrl;

    // JSON-LD Organization snippet — emitted on every public page (Google
    // dedupes per-site identity claims; emitting once per page is fine).
    // Sources fall through gracefully; missing fields are simply omitted.
    const orgJson: Record<string, unknown> = {
        '@context': 'https://schema.org',
        '@type': 'Organization',
        name: siteName,
        url: origin || undefined,
    };
    if (branding?.logo_url) orgJson.logo = branding.logo_url;
    const socials = branding?.socials;
    if (socials) {
        const sameAs = Object.values(socials).filter(
            (v): v is string => typeof v === 'string' && v.trim() !== '',
        );
        if (sameAs.length > 0) orgJson.sameAs = sameAs;
    }
    if (branding?.support_email) {
        orgJson.contactPoint = {
            '@type': 'ContactPoint',
            email: branding.support_email,
            contactType: 'customer support',
        };
    }
    const jsonLd = JSON.stringify(orgJson);

    // Additional JSON-LD blocks emitted on this page:
    //   - FAQPage: auto-built from any `faq` sections on this page
    //   - Caller extras: pre-serialized schemas the page wants emitted
    //     (e.g. Product/AggregateOffer for /pricing — built server-side
    //     since it needs plan data the client doesn't have at render)
    const jsonLdBlocks: string[] = [jsonLd];
    const faqJson = sections ? buildFaqJsonLd(sections) : null;
    if (faqJson) jsonLdBlocks.push(faqJson);
    if (extraJsonLd) jsonLdBlocks.push(...extraJsonLd);

    return (
        <Head>
            <title>{title}</title>
            {description && <meta name="description" content={description} />}
            {keywords && <meta name="keywords" content={keywords} />}
            {noindex && <meta name="robots" content="noindex,nofollow" />}

            {/* Canonical — strips tracking params so ?utm_source variants
                don't fragment Google's view of the page. */}
            <link rel="canonical" href={canonicalUrl} />

            {/* Open Graph */}
            <meta property="og:title" content={title} />
            {description && <meta property="og:description" content={description} />}
            {ogImage && <meta property="og:image" content={ogImage} />}
            <meta property="og:url" content={ogUrl} />
            <meta property="og:type" content="website" />

            {/* Twitter */}
            <meta
                name="twitter:card"
                content={ogImage ? 'summary_large_image' : 'summary'}
            />
            <meta name="twitter:title" content={title} />
            {description && <meta name="twitter:description" content={description} />}
            {ogImage && <meta name="twitter:image" content={ogImage} />}
            {twitterHandle && <meta name="twitter:site" content={twitterHandle} />}

            {/* JSON-LD blocks: Organization (site identity, every page),
                FAQPage (auto-built from `faq` sections), plus any extras
                the page passed in (Product/Offer on /pricing, etc).
                Each emitted as its own <script> per schema.org convention —
                avoids array-style nesting that some validators reject. */}
            {jsonLdBlocks.map((blk, i) => (
                <script
                    key={i}
                    type="application/ld+json"
                    dangerouslySetInnerHTML={{ __html: blk }}
                />
            ))}
        </Head>
    );
}
