<?php
/**
 * firefly-schema.php
 * Firefly Schema (Customizer + JSON-LD @graph output + Customizer JSON preview)
 *
 * Features:
 * - Appearance > Customize > "Firefly Schema" section
 * - Outputs JSON-LD into <head> as a single @graph (WebSite + main entity)
 * - Optional: In the Customizer preview pane, hide the website and show a black “code view” of the JSON
 */

if (!defined('ABSPATH')) exit;

/* ------------------------------------------------------------
 * Type lists (curated)
 * ------------------------------------------------------------ */
function firefly_schema_primary_choices(): array {
    return [
        'WebSite'       => 'WebSite (just the site)',
        'Person'        => 'Person (personal site / portfolio)',
        'Organization'  => 'Organization (company / group / non-profit)',
        'LocalBusiness' => 'LocalBusiness (shop / service / physical business)',
        'Place'         => 'Place (a location / venue / attraction)',
        'Event'         => 'Event (an event listing / gig / workshop)',
        'MusicGroup'    => 'MusicGroup (band / artist collective)',
        'CreativeWork'  => 'CreativeWork (content-focused site)',
    ];
}

function firefly_schema_org_subtypes(): array {
    return [
        ''                        => '— None (Organization) —',
        'Corporation'             => 'Corporation',
        'EducationalOrganization' => 'EducationalOrganization',
        'NGO'                     => 'NGO',
        'GovernmentOrganization'  => 'GovernmentOrganization',
        'PerformingGroup'         => 'PerformingGroup',
        'SportsOrganization'      => 'SportsOrganization',
        'Project'                 => 'Project',
    ];
}

function firefly_schema_localbusiness_subtypes(): array {
    return [
        '' => '— None (LocalBusiness) —',
        'AccountingService' => 'AccountingService',
        'AutoDealer' => 'AutoDealer',
        'AutoRepair' => 'AutoRepair',
        'Bakery' => 'Bakery',
        'BarOrPub' => 'BarOrPub',
        'BeautySalon' => 'BeautySalon',
        'BedAndBreakfast' => 'BedAndBreakfast',
        'CafeOrCoffeeShop' => 'CafeOrCoffeeShop',
        'ChildCare' => 'ChildCare',
        'Dentist' => 'Dentist',
        'DryCleaningOrLaundry' => 'DryCleaningOrLaundry',
        'Electrician' => 'Electrician',
        'EmploymentAgency' => 'EmploymentAgency',
        'Florist' => 'Florist',
        'FuneralHome' => 'FuneralHome',
        'GeneralContractor' => 'GeneralContractor',
        'HairSalon' => 'HairSalon',
        'HealthClub' => 'HealthClub',
        'HomeAndConstructionBusiness' => 'HomeAndConstructionBusiness',
        'Hospital' => 'Hospital',
        'Hotel' => 'Hotel',
        'InsuranceAgency' => 'InsuranceAgency',
        'LegalService' => 'LegalService (law firm / legal practice)',
        'Library' => 'Library',
        'LodgingBusiness' => 'LodgingBusiness',
        'MedicalBusiness' => 'MedicalBusiness',
        'Motel' => 'Motel',
        'MovingCompany' => 'MovingCompany',
        'NightClub' => 'NightClub',
        'Notary' => 'Notary',
        'Optician' => 'Optician',
        'Pharmacy' => 'Pharmacy',
        'Physician' => 'Physician',
        'Plumber' => 'Plumber',
        'ProfessionalService' => 'ProfessionalService (generic)',
        'RealEstateAgent' => 'RealEstateAgent',
        'Restaurant' => 'Restaurant',
        'RoofingContractor' => 'RoofingContractor',
        'School' => 'School',
        'SelfStorage' => 'SelfStorage',
        'Store' => 'Store (generic)',
        'TattooParlor' => 'TattooParlor',
        'TouristAttraction' => 'TouristAttraction',
        'TravelAgency' => 'TravelAgency',
        'VeterinaryCare' => 'VeterinaryCare',
    ];
}

function firefly_schema_creativework_subtypes(): array {
    return [
        '' => '— None (CreativeWork) —',
        'Article' => 'Article',
        'Blog' => 'Blog',
        'BlogPosting' => 'BlogPosting',
        'Book' => 'Book',
        'Course' => 'Course',
        'ImageGallery' => 'ImageGallery (photography portfolio)',
        'Photograph' => 'Photograph',
        'VideoObject' => 'VideoObject',
        'MusicAlbum' => 'MusicAlbum',
        'MusicRecording' => 'MusicRecording',
        'PodcastSeries' => 'PodcastSeries',
        'SoftwareApplication' => 'SoftwareApplication',
    ];
}

function firefly_schema_place_subtypes(): array {
    return [
        '' => '— None (Place) —',
        'TouristAttraction' => 'TouristAttraction',
        'CivicStructure' => 'CivicStructure',
        'LandmarksOrHistoricalBuildings' => 'LandmarksOrHistoricalBuildings',
        'EventVenue' => 'EventVenue',
        'Museum' => 'Museum',
        'Park' => 'Park',
        'StadiumOrArena' => 'StadiumOrArena',
    ];
}

function firefly_schema_event_subtypes(): array {
    return [
        '' => '— None (Event) —',
        'BusinessEvent' => 'BusinessEvent',
        'ChildrensEvent' => 'ChildrensEvent',
        'ComedyEvent' => 'ComedyEvent',
        'DanceEvent' => 'DanceEvent',
        'EducationEvent' => 'EducationEvent',
        'ExhibitionEvent' => 'ExhibitionEvent',
        'Festival' => 'Festival',
        'FoodEvent' => 'FoodEvent',
        'LiteraryEvent' => 'LiteraryEvent',
        'MusicEvent' => 'MusicEvent',
        'SalesEvent' => 'SalesEvent',
        'ScreeningEvent' => 'ScreeningEvent',
        'SocialEvent' => 'SocialEvent',
        'SportsEvent' => 'SportsEvent',
        'TheaterEvent' => 'TheaterEvent',
        'VisualArtsEvent' => 'VisualArtsEvent',
    ];
}

/* ------------------------------------------------------------
 * Sanitizers & helpers
 * ------------------------------------------------------------ */
function firefly_schema_sanitize_select($value, array $allowed, string $fallback = ''): string {
    $value = trim((string)$value);
    return array_key_exists($value, $allowed) ? $value : $fallback;
}

function firefly_schema_sanitize_textarea($value) {
    $value = (string)$value;
    $value = wp_strip_all_tags($value);
    $value = str_replace(["\r\n", "\r"], "\n", $value);
    return trim($value);
}

function firefly_schema_sanitize_phone($value) {
    $value = (string)$value;
    $value = wp_strip_all_tags($value);
    $value = preg_replace('/[^0-9\+\-\(\)\s]/', '', $value);
    return trim($value);
}

function firefly_schema_sanitize_country($value) {
    $value = strtoupper(trim((string)$value));
    $value = preg_replace('/[^A-Z]/', '', $value);
    if (strlen($value) > 3) $value = substr($value, 0, 3);
    return $value;
}

function firefly_schema_sanitize_float($value) {
    $value = trim((string)$value);
    $value = str_replace(',', '.', $value);
    $value = preg_replace('/[^0-9\.\-]/', '', $value);
    if ($value === '' || $value === '-' || $value === '.') return '';
    return $value;
}

function firefly_schema_sanitize_url_lines($value) {
    $value = (string)$value;
    $value = str_replace(["\r\n", "\r"], "\n", $value);
    $lines = array_filter(array_map('trim', explode("\n", $value)));
    $out = [];
    foreach ($lines as $line) {
        $u = esc_url_raw($line);
        if ($u) $out[] = $u;
    }
    return implode("\n", $out);
}

function firefly_schema_parse_url_lines($value): array {
    $value = (string)$value;
    $value = str_replace(["\r\n", "\r"], "\n", $value);
    $lines = array_filter(array_map('trim', explode("\n", $value)));
    $out = [];
    foreach ($lines as $line) {
        $u = esc_url_raw($line);
        if ($u) $out[] = $u;
    }
    return array_values(array_unique($out));
}

function firefly_schema_sanitize_opening_hours($value) {
    return firefly_schema_sanitize_textarea($value);
}

/**
 * Parse opening hours lines like:
 * Mo-Fr 09:00-17:00
 * Sa 10:00-14:00
 * Su closed
 */
function firefly_schema_parse_opening_hours($raw): array {
    $raw = (string)$raw;
    $raw = str_replace(["\r\n", "\r"], "\n", $raw);
    $lines = array_filter(array_map('trim', explode("\n", $raw)));
    if (empty($lines)) return [];

    $dayMap = [
        'Mo' => 'Monday',
        'Tu' => 'Tuesday',
        'We' => 'Wednesday',
        'Th' => 'Thursday',
        'Fr' => 'Friday',
        'Sa' => 'Saturday',
        'Su' => 'Sunday',
    ];
    $order = ['Mo','Tu','We','Th','Fr','Sa','Su'];

    $out = [];

    foreach ($lines as $line) {
        $line = preg_replace('/\s+/', ' ', $line);

        if (preg_match('/^([A-Za-z]{2})(?:-([A-Za-z]{2}))?\s+closed$/i', $line)) {
            continue;
        }

        if (preg_match('/^([A-Za-z]{2})(?:-([A-Za-z]{2}))?\s+([0-2]\d:\d\d)\-([0-2]\d:\d\d)$/', $line, $m)) {
            $d1 = ucfirst(strtolower($m[1]));
            $d2 = isset($m[2]) ? ucfirst(strtolower($m[2])) : '';
            if (!isset($dayMap[$d1])) continue;
            if ($d2 !== '' && !isset($dayMap[$d2])) continue;

            $spec = [
                '@type'  => 'OpeningHoursSpecification',
                'opens'  => $m[3],
                'closes' => $m[4],
            ];

            if ($d2 === '' || $d1 === $d2) {
                $spec['dayOfWeek'] = $dayMap[$d1];
            } else {
                $i1 = array_search($d1, $order, true);
                $i2 = array_search($d2, $order, true);
                if ($i1 === false || $i2 === false) continue;

                $days = [];
                if ($i1 <= $i2) {
                    for ($i = $i1; $i <= $i2; $i++) $days[] = $order[$i];
                } else {
                    for ($i = $i1; $i < count($order); $i++) $days[] = $order[$i];
                    for ($i = 0; $i <= $i2; $i++) $days[] = $order[$i];
                }
                $spec['dayOfWeek'] = array_map(fn($d) => $dayMap[$d] ?? $d, $days);
            }

            $out[] = $spec;
        }
    }

    return $out;
}

/* ------------------------------------------------------------
 * Customizer controls
 * ------------------------------------------------------------ */
add_action('customize_register', 'firefly_schema_customizer_register');
function firefly_schema_customizer_register($wp_customize) {

    $wp_customize->add_section('firefly_schema_section', [
        'title'       => __('Firefly Schema', 'firefly'),
        'priority'    => 30,
        'description' => __('Structured data (JSON-LD) added to your site header. Uses @graph (WebSite + main entity).', 'firefly'),
    ]);

    // Helpers
    $add_bool = function($id, $label, $desc = '') use ($wp_customize) {
        $wp_customize->add_setting($id, [
            'default' => false,
            'sanitize_callback' => 'wp_validate_boolean',
        ]);
        $wp_customize->add_control($id, [
            'label' => $label,
            'description' => $desc,
            'section' => 'firefly_schema_section',
            'type' => 'checkbox',
        ]);
    };

    $add_text = function($id, $label, $desc = '', $sanitize = 'sanitize_text_field') use ($wp_customize) {
        $wp_customize->add_setting($id, [
            'default' => '',
            'sanitize_callback' => $sanitize,
        ]);
        $wp_customize->add_control($id, [
            'label' => $label,
            'description' => $desc,
            'section' => 'firefly_schema_section',
            'type' => 'text',
        ]);
    };

    $add_textarea = function($id, $label, $desc = '', $sanitize = 'firefly_schema_sanitize_textarea') use ($wp_customize) {
        $wp_customize->add_setting($id, [
            'default' => '',
            'sanitize_callback' => $sanitize,
        ]);
        $wp_customize->add_control($id, [
            'label' => $label,
            'description' => $desc,
            'section' => 'firefly_schema_section',
            'type' => 'textarea',
        ]);
    };

    $add_image = function($id, $label, $desc = '') use ($wp_customize) {
        $wp_customize->add_setting($id, [
            'default' => '',
            'sanitize_callback' => 'esc_url_raw',
        ]);
        $wp_customize->add_control(new WP_Customize_Image_Control($wp_customize, $id, [
            'label' => $label,
            'description' => $desc,
            'section' => 'firefly_schema_section',
            'settings' => $id,
        ]));
    };

    // Enable schema + preview switch
    $add_bool('firefly_schema_enable', __('Enable Schema', 'firefly'), __('Outputs JSON-LD into the header (wp_head).', 'firefly'));
    $add_bool('firefly_schema_preview_json', __('Customizer Preview: show JSON instead of website', 'firefly'), __('Replaces the right-hand preview with a JSON code view.', 'firefly'));

    // Primary type
    $primary_choices = firefly_schema_primary_choices();
    $wp_customize->add_setting('firefly_schema_primary_type', [
        'default' => 'WebSite',
        'sanitize_callback' => function($value) use ($primary_choices) {
            return firefly_schema_sanitize_select($value, $primary_choices, 'WebSite');
        },
    ]);
    $wp_customize->add_control('firefly_schema_primary_type', [
        'label'   => __('Primary Schema Type (main entity)', 'firefly'),
        'section' => 'firefly_schema_section',
        'type'    => 'select',
        'choices' => $primary_choices,
    ]);

    // Subtypes (shown conditionally)
    $org_sub = firefly_schema_org_subtypes();
    $wp_customize->add_setting('firefly_schema_org_subtype', [
        'default' => '',
        'sanitize_callback' => function($value) use ($org_sub) { return firefly_schema_sanitize_select($value, $org_sub, ''); },
    ]);
    $wp_customize->add_control('firefly_schema_org_subtype', [
        'label' => __('Organization Subtype (optional)', 'firefly'),
        'section' => 'firefly_schema_section',
        'type' => 'select',
        'choices' => $org_sub,
        'active_callback' => function() {
            return get_theme_mod('firefly_schema_primary_type', 'WebSite') === 'Organization';
        },
    ]);

    $lb_sub = firefly_schema_localbusiness_subtypes();
    $wp_customize->add_setting('firefly_schema_localbusiness_subtype', [
        'default' => '',
        'sanitize_callback' => function($value) use ($lb_sub) { return firefly_schema_sanitize_select($value, $lb_sub, ''); },
    ]);
    $wp_customize->add_control('firefly_schema_localbusiness_subtype', [
        'label' => __('LocalBusiness Subtype (optional)', 'firefly'),
        'section' => 'firefly_schema_section',
        'type' => 'select',
        'choices' => $lb_sub,
        'active_callback' => function() {
            return get_theme_mod('firefly_schema_primary_type', 'WebSite') === 'LocalBusiness';
        },
    ]);

    $cw_sub = firefly_schema_creativework_subtypes();
    $wp_customize->add_setting('firefly_schema_creativework_subtype', [
        'default' => '',
        'sanitize_callback' => function($value) use ($cw_sub) { return firefly_schema_sanitize_select($value, $cw_sub, ''); },
    ]);
    $wp_customize->add_control('firefly_schema_creativework_subtype', [
        'label' => __('CreativeWork Subtype (optional)', 'firefly'),
        'section' => 'firefly_schema_section',
        'type' => 'select',
        'choices' => $cw_sub,
        'active_callback' => function() {
            return get_theme_mod('firefly_schema_primary_type', 'WebSite') === 'CreativeWork';
        },
    ]);

    $pl_sub = firefly_schema_place_subtypes();
    $wp_customize->add_setting('firefly_schema_place_subtype', [
        'default' => '',
        'sanitize_callback' => function($value) use ($pl_sub) { return firefly_schema_sanitize_select($value, $pl_sub, ''); },
    ]);
    $wp_customize->add_control('firefly_schema_place_subtype', [
        'label' => __('Place Subtype (optional)', 'firefly'),
        'section' => 'firefly_schema_section',
        'type' => 'select',
        'choices' => $pl_sub,
        'active_callback' => function() {
            return get_theme_mod('firefly_schema_primary_type', 'WebSite') === 'Place';
        },
    ]);

    $ev_sub = firefly_schema_event_subtypes();
    $wp_customize->add_setting('firefly_schema_event_subtype', [
        'default' => '',
        'sanitize_callback' => function($value) use ($ev_sub) { return firefly_schema_sanitize_select($value, $ev_sub, ''); },
    ]);
    $wp_customize->add_control('firefly_schema_event_subtype', [
        'label' => __('Event Subtype (optional)', 'firefly'),
        'section' => 'firefly_schema_section',
        'type' => 'select',
        'choices' => $ev_sub,
        'active_callback' => function() {
            return get_theme_mod('firefly_schema_primary_type', 'WebSite') === 'Event';
        },
    ]);

    // Shared main-entity fields
    $add_text('firefly_schema_name', __('Name', 'firefly'), __('Name of the main entity (person/org/business/place/event).', 'firefly'));
    $add_text('firefly_schema_url', __('URL', 'firefly'), __('Leave blank to use your site URL.', 'firefly'), 'esc_url_raw');
    $add_textarea('firefly_schema_description', __('Description', 'firefly'), __('Short description.', 'firefly'));
    $add_image('firefly_schema_logo', __('Logo (optional)', 'firefly'));
    $add_image('firefly_schema_image', __('Primary Image (optional)', 'firefly'));

    $add_text('firefly_schema_phone', __('Telephone (optional)', 'firefly'), '', 'firefly_schema_sanitize_phone');
    $add_text('firefly_schema_email', __('Email (optional)', 'firefly'), '', 'sanitize_email');
    $add_text('firefly_schema_price_range', __('Price Range (optional)', 'firefly'), __('Example: $$ or $10-$50', 'firefly'));

    $add_textarea(
        'firefly_schema_sameas',
        __('sameAs / Social Links (optional)', 'firefly'),
        __("One URL per line.\nhttps://facebook.com/...\nhttps://instagram.com/...", 'firefly'),
        'firefly_schema_sanitize_url_lines'
    );

    // Address & geo (business/place/event location)
    $add_text('firefly_schema_street', __('Street Address (optional)', 'firefly'));
    $add_text('firefly_schema_locality', __('City / Locality (optional)', 'firefly'));
    $add_text('firefly_schema_region', __('Region / State (optional)', 'firefly'));
    $add_text('firefly_schema_postcode', __('Postal Code (optional)', 'firefly'));
    $add_text('firefly_schema_country', __('Country (optional)', 'firefly'), __('Example: NZ, VN, AU, US', 'firefly'), 'firefly_schema_sanitize_country');

    $add_text('firefly_schema_lat', __('Latitude (optional)', 'firefly'), '', 'firefly_schema_sanitize_float');
    $add_text('firefly_schema_lng', __('Longitude (optional)', 'firefly'), '', 'firefly_schema_sanitize_float');

    $add_textarea(
        'firefly_schema_opening_hours',
        __('Opening Hours (optional)', 'firefly'),
        __("One per line, e.g.\nMo-Fr 09:00-17:00\nSa 10:00-14:00\nSu closed", 'firefly'),
        'firefly_schema_sanitize_opening_hours'
    );

    // Event-specific fields (shown only if primary = Event)
    $is_event = function() {
        return get_theme_mod('firefly_schema_primary_type', 'WebSite') === 'Event';
    };

    $wp_customize->add_setting('firefly_schema_event_start', [
        'default' => '',
        'sanitize_callback' => 'sanitize_text_field',
    ]);
    $wp_customize->add_control('firefly_schema_event_start', [
        'label' => __('Event Start DateTime', 'firefly'),
        'description' => __('Use ISO format like 2026-03-10T19:30 (recommended).', 'firefly'),
        'section' => 'firefly_schema_section',
        'type' => 'text',
        'active_callback' => $is_event,
    ]);

    $wp_customize->add_setting('firefly_schema_event_end', [
        'default' => '',
        'sanitize_callback' => 'sanitize_text_field',
    ]);
    $wp_customize->add_control('firefly_schema_event_end', [
        'label' => __('Event End DateTime (optional)', 'firefly'),
        'description' => __('Use ISO format like 2026-03-10T22:00.', 'firefly'),
        'section' => 'firefly_schema_section',
        'type' => 'text',
        'active_callback' => $is_event,
    ]);

    $wp_customize->add_setting('firefly_schema_event_location_name', [
        'default' => '',
        'sanitize_callback' => 'sanitize_text_field',
    ]);
    $wp_customize->add_control('firefly_schema_event_location_name', [
        'label' => __('Event Location Name (optional)', 'firefly'),
        'description' => __('Optional venue name. Address/Geo comes from the Address fields above.', 'firefly'),
        'section' => 'firefly_schema_section',
        'type' => 'text',
        'active_callback' => $is_event,
    ]);

    $wp_customize->add_setting('firefly_schema_event_offer_url', [
        'default' => '',
        'sanitize_callback' => 'esc_url_raw',
    ]);
    $wp_customize->add_control('firefly_schema_event_offer_url', [
        'label' => __('Ticket / Offer URL (optional)', 'firefly'),
        'section' => 'firefly_schema_section',
        'type' => 'text',
        'active_callback' => $is_event,
    ]);

    $wp_customize->add_setting('firefly_schema_event_offer_price', [
        'default' => '',
        'sanitize_callback' => 'sanitize_text_field',
    ]);
    $wp_customize->add_control('firefly_schema_event_offer_price', [
        'label' => __('Ticket Price (optional)', 'firefly'),
        'description' => __('Example: 20 or 20.00', 'firefly'),
        'section' => 'firefly_schema_section',
        'type' => 'text',
        'active_callback' => $is_event,
    ]);

    $wp_customize->add_setting('firefly_schema_event_offer_currency', [
        'default' => '',
        'sanitize_callback' => 'sanitize_text_field',
    ]);
    $wp_customize->add_control('firefly_schema_event_offer_currency', [
        'label' => __('Ticket Currency (optional)', 'firefly'),
        'description' => __('Example: NZD, USD, VND', 'firefly'),
        'section' => 'firefly_schema_section',
        'type' => 'text',
        'active_callback' => $is_event,
    ]);
}

/* ------------------------------------------------------------
 * Build schema graph (returns array)
 * ------------------------------------------------------------ */
function firefly_schema_build_graph(): array {
    $site_url  = home_url('/');
    $site_name = get_bloginfo('name');
    $site_desc = get_bloginfo('description');

    $primary = (string)get_theme_mod('firefly_schema_primary_type', 'WebSite');
    $type = $primary;

    if ($primary === 'Organization') {
        $sub = (string)get_theme_mod('firefly_schema_org_subtype', '');
        if ($sub !== '') $type = $sub;
    } elseif ($primary === 'LocalBusiness') {
        $sub = (string)get_theme_mod('firefly_schema_localbusiness_subtype', '');
        if ($sub !== '') $type = $sub;
    } elseif ($primary === 'CreativeWork') {
        $sub = (string)get_theme_mod('firefly_schema_creativework_subtype', '');
        if ($sub !== '') $type = $sub;
    } elseif ($primary === 'Place') {
        $sub = (string)get_theme_mod('firefly_schema_place_subtype', '');
        if ($sub !== '') $type = $sub;
    } elseif ($primary === 'Event') {
        $sub = (string)get_theme_mod('firefly_schema_event_subtype', '');
        if ($sub !== '') $type = $sub;
    }

    $url = trim((string)get_theme_mod('firefly_schema_url', ''));
    if ($url === '') $url = $site_url;

    $name = trim((string)get_theme_mod('firefly_schema_name', ''));
    if ($name === '') $name = $site_name;

    $desc  = trim((string)get_theme_mod('firefly_schema_description', ''));
    $logo  = trim((string)get_theme_mod('firefly_schema_logo', ''));
    $image = trim((string)get_theme_mod('firefly_schema_image', ''));

    $phone = trim((string)get_theme_mod('firefly_schema_phone', ''));
    $email = trim((string)get_theme_mod('firefly_schema_email', ''));
    $price = trim((string)get_theme_mod('firefly_schema_price_range', ''));

    $sameas = firefly_schema_parse_url_lines((string)get_theme_mod('firefly_schema_sameas', ''));

    // Address / geo
    $street   = trim((string)get_theme_mod('firefly_schema_street', ''));
    $locality = trim((string)get_theme_mod('firefly_schema_locality', ''));
    $region   = trim((string)get_theme_mod('firefly_schema_region', ''));
    $postcode = trim((string)get_theme_mod('firefly_schema_postcode', ''));
    $country  = trim((string)get_theme_mod('firefly_schema_country', ''));

    $lat = trim((string)get_theme_mod('firefly_schema_lat', ''));
    $lng = trim((string)get_theme_mod('firefly_schema_lng', ''));

    $opening_specs = firefly_schema_parse_opening_hours((string)get_theme_mod('firefly_schema_opening_hours', ''));

    $has_address = ($street || $locality || $region || $postcode || $country);
    $address_obj = null;
    if ($has_address) {
        $address_obj = ['@type' => 'PostalAddress'];
        if ($street)   $address_obj['streetAddress'] = $street;
        if ($locality) $address_obj['addressLocality'] = $locality;
        if ($region)   $address_obj['addressRegion'] = $region;
        if ($postcode) $address_obj['postalCode'] = $postcode;
        if ($country)  $address_obj['addressCountry'] = $country;
    }

    $geo_obj = null;
    if ($lat !== '' && $lng !== '') {
        $geo_obj = [
            '@type' => 'GeoCoordinates',
            'latitude' => (float)$lat,
            'longitude' => (float)$lng,
        ];
    }

    // IDs
    $website_id = trailingslashit($site_url) . '#website';
    $entity_id  = trailingslashit($url) . '#entity';

    // WebSite node
    $website_node = [
        '@type' => 'WebSite',
        '@id'   => $website_id,
        'url'   => $site_url,
        'name'  => $site_name,
    ];
    if (!empty($site_desc)) $website_node['description'] = $site_desc;

    if ($primary !== 'WebSite') {
        $website_node['about'] = ['@id' => $entity_id];
    }

    // Main entity node
    $entity_node = [
        '@type' => $type,
        '@id'   => $entity_id,
        'name'  => $name,
        'url'   => $url,
    ];

    if ($desc !== '')  $entity_node['description'] = $desc;
    if ($logo !== '')  $entity_node['logo'] = $logo;
    if ($image !== '') $entity_node['image'] = $image;
    if ($phone !== '') $entity_node['telephone'] = $phone;
    if ($email !== '') $entity_node['email'] = $email;
    if ($price !== '') $entity_node['priceRange'] = $price;
    if (!empty($sameas)) $entity_node['sameAs'] = array_values($sameas);

    if ($address_obj) $entity_node['address'] = $address_obj;
    if ($geo_obj)     $entity_node['geo'] = $geo_obj;
    if (!empty($opening_specs)) $entity_node['openingHoursSpecification'] = $opening_specs;

    // Event-specific
    if ($primary === 'Event') {
        $start = trim((string)get_theme_mod('firefly_schema_event_start', ''));
        $end   = trim((string)get_theme_mod('firefly_schema_event_end', ''));
        if ($start !== '') $entity_node['startDate'] = $start;
        if ($end !== '')   $entity_node['endDate'] = $end;

        $loc_name = trim((string)get_theme_mod('firefly_schema_event_location_name', ''));
        if ($address_obj || $geo_obj || $loc_name !== '') {
            $loc = ['@type' => 'Place'];
            if ($loc_name !== '') $loc['name'] = $loc_name;
            if ($address_obj) $loc['address'] = $address_obj;
            if ($geo_obj)     $loc['geo'] = $geo_obj;
            $entity_node['location'] = $loc;
        }

        $offer_url = trim((string)get_theme_mod('firefly_schema_event_offer_url', ''));
        $offer_price = trim((string)get_theme_mod('firefly_schema_event_offer_price', ''));
        $offer_currency = trim((string)get_theme_mod('firefly_schema_event_offer_currency', ''));

        if ($offer_url !== '' || $offer_price !== '' || $offer_currency !== '') {
            $offer = ['@type' => 'Offer'];
            if ($offer_url !== '') $offer['url'] = $offer_url;
            if ($offer_price !== '') $offer['price'] = $offer_price;
            if ($offer_currency !== '') $offer['priceCurrency'] = strtoupper($offer_currency);
            $entity_node['offers'] = $offer;
        }
    }

    return [
        '@context' => 'https://schema.org',
        '@graph' => [
            $website_node,
            $entity_node,
        ],
    ];
}

/* ------------------------------------------------------------
 * Output JSON-LD into header
 * ------------------------------------------------------------ */
add_action('wp_head', 'firefly_schema_output_jsonld_graph', 20);
function firefly_schema_output_jsonld_graph() {
    if (is_admin()) return;
    if (!(bool)get_theme_mod('firefly_schema_enable', false)) return;

    $graph = firefly_schema_build_graph();

    echo "\n<script type=\"application/ld+json\">\n";
    echo wp_json_encode($graph, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
    echo "\n</script>\n";
}

/* ------------------------------------------------------------
 * Customizer Preview override (replace website preview with JSON code view)
 * ------------------------------------------------------------ */
add_action('customize_preview_init', 'firefly_schema_customizer_preview_init');
function firefly_schema_customizer_preview_init() {
    // Only run inside Customizer preview iframe
    if (!is_customize_preview()) return;

    // Only if user enabled the toggle
    if (!(bool)get_theme_mod('firefly_schema_preview_json', false)) return;

    // Hide the site and paint the background black
    add_action('wp_head', function() {
        echo '<style id="firefly-schema-json-preview-style">
            html, body { height: 100% !important; }
            body {
                background: #0f0f10 !important;
                margin: 0 !important;
                font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !important;
            }
            body > *:not(#firefly-schema-preview-wrap) { display: none !important; }
            #firefly-schema-preview-wrap { padding: 24px; }
            #firefly-schema-preview-title {
                color: #c8c8c8; font-size: 13px; margin: 0 0 12px 0; opacity: 0.9;
            }
            #firefly-schema-preview {
                white-space: pre;
                font-size: 13px;
                line-height: 1.55;
                color: #d4d4d4;
                overflow: auto;
                border: 1px solid rgba(255,255,255,0.08);
                border-radius: 10px;
                padding: 16px;
                background: rgba(0,0,0,0.35);
            }
            .ff-key { color: #9cdcfe; }
            .ff-string { color: #ce9178; }
            .ff-number { color: #b5cea8; }
            .ff-boolean { color: #569cd6; }
            .ff-null { color: #569cd6; opacity: 0.8; }
        </style>';
    }, 999);

    add_action('wp_footer', function() {
        $graph = firefly_schema_build_graph();
        $json  = wp_json_encode($graph, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
        $pretty = firefly_schema_syntax_highlight($json);

        echo '<div id="firefly-schema-preview-wrap">';
        echo '<div id="firefly-schema-preview-title">Firefly Schema JSON-LD (@graph preview)</div>';
        echo '<div id="firefly-schema-preview">' . $pretty . '</div>';
        echo '</div>';
    }, 999);
}

/**
 * Very simple syntax highlighting for JSON (safe HTML output).
 */
function firefly_schema_syntax_highlight(string $json): string {
    $json = htmlspecialchars($json, ENT_QUOTES, 'UTF-8');

    // keys: "key":
    $json = preg_replace('/"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"\s*:/', '<span class="ff-key">"$1"</span>:', $json);

    // strings: : "value"
    $json = preg_replace('/:\s*"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"/', ': <span class="ff-string">"$1"</span>', $json);

    // numbers: : 123 or : 12.34
    $json = preg_replace('/:\s*(-?\d+(?:\.\d+)?)/', ': <span class="ff-number">$1</span>', $json);

    // booleans + null
    $json = preg_replace('/:\s*(true|false)\b/', ': <span class="ff-boolean">$1</span>', $json);
    $json = preg_replace('/:\s*(null)\b/', ': <span class="ff-null">$1</span>', $json);

    return $json;
}
