<?php

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

class SEO_Fury_Local_SEO {
    public function __construct() {
        // On singular pages the main schema block is built by SEO_Fury_Post_Meta_Schema
        // and we inject our node there via the filter so everything ends up in one @graph.
        // On all other contexts (archives, home, etc.) we fall back to a standalone block.
        add_filter( 'seo_fury_schema_nodes', array( $this, 'inject_schema_node' ), 10, 2 );
        add_action( 'wp_head', array( $this, 'output_schema' ), 1 );
    }

    /**
     * Returns the ordered list of Schema.org day abbreviations mapped to display names.
     */
    private function get_days() {
        return array(
            'Mo' => __( 'Monday',    'seo-fury' ),
            'Tu' => __( 'Tuesday',   'seo-fury' ),
            'We' => __( 'Wednesday', 'seo-fury' ),
            'Th' => __( 'Thursday',  'seo-fury' ),
            'Fr' => __( 'Friday',    'seo-fury' ),
            'Sa' => __( 'Saturday',  'seo-fury' ),
            'Su' => __( 'Sunday',    'seo-fury' ),
        );
    }

    public function register_settings() {
        $sanitize_checkbox  = array( 'sanitize_callback' => 'absint' );
        $sanitize_text      = array( 'sanitize_callback' => 'sanitize_text_field' );
        $sanitize_url       = array( 'sanitize_callback' => 'esc_url_raw' );
        $sanitize_email     = array( 'sanitize_callback' => 'sanitize_email' );

        register_setting( 'seo_fury_local_group', 'seo_fury_local_enabled', $sanitize_checkbox );
        register_setting( 'seo_fury_local_group', 'seo_fury_local_type', $sanitize_text );
        register_setting( 'seo_fury_local_group', 'seo_fury_local_name', $sanitize_text );
        register_setting( 'seo_fury_local_group', 'seo_fury_local_phone', $sanitize_text );
        register_setting( 'seo_fury_local_group', 'seo_fury_local_email', $sanitize_email );
        register_setting( 'seo_fury_local_group', 'seo_fury_local_url', $sanitize_url );
        register_setting( 'seo_fury_local_group', 'seo_fury_local_logo', $sanitize_url );
        register_setting( 'seo_fury_local_group', 'seo_fury_local_image', $sanitize_url );
        register_setting( 'seo_fury_local_group', 'seo_fury_local_price_range', $sanitize_text );
        register_setting( 'seo_fury_local_group', 'seo_fury_local_street', $sanitize_text );
        register_setting( 'seo_fury_local_group', 'seo_fury_local_city', $sanitize_text );
        register_setting( 'seo_fury_local_group', 'seo_fury_local_region', $sanitize_text );
        register_setting( 'seo_fury_local_group', 'seo_fury_local_postal', $sanitize_text );
        register_setting( 'seo_fury_local_group', 'seo_fury_local_country', $sanitize_text );
        register_setting( 'seo_fury_local_group', 'seo_fury_local_lat', $sanitize_text );
        register_setting( 'seo_fury_local_group', 'seo_fury_local_lng', $sanitize_text );
        register_setting( 'seo_fury_local_group', 'seo_fury_local_opening_hours',
            array( 'sanitize_callback' => array( $this, 'sanitize_opening_hours' ) )
        );

        add_settings_section(
            'seo_fury_local_section',
            '',
            null,
            'seo_fury_local_group'
        );

        add_settings_field( 'seo_fury_local_enabled',       __( 'Enable Local SEO', 'seo-fury' ),  array( $this, 'field_enabled' ),        'seo_fury_local_group', 'seo_fury_local_section' );
        add_settings_field( 'seo_fury_local_type',          __( 'Business Type', 'seo-fury' ),     array( $this, 'field_type' ),           'seo_fury_local_group', 'seo_fury_local_section' );
        add_settings_field( 'seo_fury_local_name',          __( 'Business Name', 'seo-fury' ),     array( $this, 'field_text' ),           'seo_fury_local_group', 'seo_fury_local_section', array( 'key' => 'seo_fury_local_name' ) );
        add_settings_field( 'seo_fury_local_phone',         __( 'Phone', 'seo-fury' ),             array( $this, 'field_text' ),           'seo_fury_local_group', 'seo_fury_local_section', array( 'key' => 'seo_fury_local_phone' ) );
        add_settings_field( 'seo_fury_local_email',         __( 'Email', 'seo-fury' ),             array( $this, 'field_text' ),           'seo_fury_local_group', 'seo_fury_local_section', array( 'key' => 'seo_fury_local_email' ) );
        add_settings_field( 'seo_fury_local_url',           __( 'URL', 'seo-fury' ),               array( $this, 'field_text' ),           'seo_fury_local_group', 'seo_fury_local_section', array( 'key' => 'seo_fury_local_url' ) );
        add_settings_field( 'seo_fury_local_logo',          __( 'Logo URL', 'seo-fury' ),          array( $this, 'field_text' ),           'seo_fury_local_group', 'seo_fury_local_section', array( 'key' => 'seo_fury_local_logo' ) );
        add_settings_field( 'seo_fury_local_image',         __( 'Image URL', 'seo-fury' ),         array( $this, 'field_text' ),           'seo_fury_local_group', 'seo_fury_local_section', array( 'key' => 'seo_fury_local_image' ) );
        add_settings_field( 'seo_fury_local_price_range',   __( 'Price Range', 'seo-fury' ),       array( $this, 'field_text' ),           'seo_fury_local_group', 'seo_fury_local_section', array( 'key' => 'seo_fury_local_price_range' ) );
        add_settings_field( 'seo_fury_local_street',        __( 'Street Address', 'seo-fury' ),    array( $this, 'field_text' ),           'seo_fury_local_group', 'seo_fury_local_section', array( 'key' => 'seo_fury_local_street' ) );
        add_settings_field( 'seo_fury_local_city',          __( 'City', 'seo-fury' ),              array( $this, 'field_text' ),           'seo_fury_local_group', 'seo_fury_local_section', array( 'key' => 'seo_fury_local_city' ) );
        add_settings_field( 'seo_fury_local_region',        __( 'Region/State', 'seo-fury' ),      array( $this, 'field_text' ),           'seo_fury_local_group', 'seo_fury_local_section', array( 'key' => 'seo_fury_local_region' ) );
        add_settings_field( 'seo_fury_local_postal',        __( 'Postal Code', 'seo-fury' ),       array( $this, 'field_text' ),           'seo_fury_local_group', 'seo_fury_local_section', array( 'key' => 'seo_fury_local_postal' ) );
        add_settings_field( 'seo_fury_local_country',       __( 'Country', 'seo-fury' ),           array( $this, 'field_text' ),           'seo_fury_local_group', 'seo_fury_local_section', array( 'key' => 'seo_fury_local_country' ) );
        add_settings_field( 'seo_fury_local_lat',           __( 'Latitude', 'seo-fury' ),          array( $this, 'field_text' ),           'seo_fury_local_group', 'seo_fury_local_section', array( 'key' => 'seo_fury_local_lat' ) );
        add_settings_field( 'seo_fury_local_lng',           __( 'Longitude', 'seo-fury' ),         array( $this, 'field_text' ),           'seo_fury_local_group', 'seo_fury_local_section', array( 'key' => 'seo_fury_local_lng' ) );
        add_settings_field( 'seo_fury_local_opening_hours', __( 'Opening Hours', 'seo-fury' ),     array( $this, 'field_opening_hours' ),  'seo_fury_local_group', 'seo_fury_local_section' );
    }

    /**
     * Sanitize the opening hours array submitted from the per-day structured form.
     *
     * Accepts an array keyed by Schema.org day codes (Mo, Tu, We, Th, Fr, Sa, Su),
     * each containing 'open' and 'close' keys with HH:MM values.
     * Days with both fields empty are treated as closed and are not stored.
     * Days where only one field is filled are discarded (the form JS prevents this).
     *
     * @param mixed $value Raw value from $_POST.
     * @return array
     */
    public function sanitize_opening_hours( $value ) {
        $allowed_days = array_keys( $this->get_days() );
        $result       = array();

        if ( ! is_array( $value ) ) {
            return $result;
        }

        $time_re = '/^\d{2}:\d{2}$/';

        foreach ( $allowed_days as $day ) {
            if ( ! isset( $value[ $day ] ) ) {
                continue;
            }

            $open  = sanitize_text_field( isset( $value[ $day ]['open'] )  ? $value[ $day ]['open']  : '' );
            $close = sanitize_text_field( isset( $value[ $day ]['close'] ) ? $value[ $day ]['close'] : '' );

            if ( $open === '' && $close === '' ) {
                // Day is closed — omit.
                continue;
            }

            // Only store when both fields are valid HH:MM values.
            if ( preg_match( $time_re, $open ) && preg_match( $time_re, $close ) ) {
                $result[ $day ] = array(
                    'open'  => $open,
                    'close' => $close,
                );
            }
        }

        return $result;
    }

    public function render_settings_page() {
        ?>
        <form method="post" action="options.php" id="seo-fury-local-form">
            <?php
            settings_fields( 'seo_fury_local_group' );
            do_settings_sections( 'seo_fury_local_group' );
            submit_button();
            ?>
        </form>
        <script>
        (function() {
            var form = document.getElementById('seo-fury-local-form');
            if (!form) return;

            /**
             * For each day row: if one time field is filled the other becomes required.
             * Blocks submission and highlights the offending field.
             */
            form.addEventListener('submit', function(e) {
                var rows = form.querySelectorAll('.seo-fury-oh-row');
                var errors = [];

                rows.forEach(function(row) {
                    var openInput  = row.querySelector('.seo-fury-oh-open');
                    var closeInput = row.querySelector('.seo-fury-oh-close');
                    if (!openInput || !closeInput) return;

                    var openVal  = openInput.value.trim();
                    var closeVal = closeInput.value.trim();

                    openInput.classList.remove('seo-fury-oh-error');
                    closeInput.classList.remove('seo-fury-oh-error');

                    if (openVal && !closeVal) {
                        closeInput.classList.add('seo-fury-oh-error');
                        errors.push(closeInput);
                    } else if (!openVal && closeVal) {
                        openInput.classList.add('seo-fury-oh-error');
                        errors.push(openInput);
                    }
                });

                if (errors.length > 0) {
                    e.preventDefault();
                    errors[0].focus();
                    alert('<?php echo esc_js( __( 'Please fill in both opening and closing time for each day, or leave both fields empty.', 'seo-fury' ) ); ?>');
                }
            });

            /**
             * Allow only digits and colon in time inputs so users cannot type
             * letters or special characters.
             */
            form.querySelectorAll('.seo-fury-oh-open, .seo-fury-oh-close').forEach(function(input) {
                input.addEventListener('keypress', function(e) {
                    var ch = String.fromCharCode(e.which);
                    if (!/[\d:]/.test(ch)) {
                        e.preventDefault();
                    }
                });

                /* Auto-insert colon after the second digit for convenience. */
                input.addEventListener('input', function() {
                    var v = input.value.replace(/[^\d:]/g, '');
                    if (v.length === 2 && input.value.slice(-1) !== ':') {
                        v = v + ':';
                    }
                    if (v.length > 5) {
                        v = v.slice(0, 5);
                    }
                    input.value = v;
                });

                /* On blur: validate HH:MM format and clear if invalid. */
                input.addEventListener('blur', function() {
                    var v = input.value.trim();
                    if (v !== '' && !/^\d{2}:\d{2}$/.test(v)) {
                        input.value = '';
                        input.classList.add('seo-fury-oh-error');
                    } else {
                        input.classList.remove('seo-fury-oh-error');
                    }
                });
            });
        }());
        </script>
        <?php
    }

    public function local_section_callback() {
        echo '<p>' . esc_html__( 'Configure your local business details for Local SEO schema.', 'seo-fury' ) . '</p>';
    }

    public function field_enabled() {
        $enabled = get_option( 'seo_fury_local_enabled', 0 );
        ?>
        <label>
            <input type="checkbox" name="seo_fury_local_enabled" value="1" <?php checked( $enabled, 1 ); ?>>
            <?php esc_html_e( 'Output LocalBusiness schema on the frontend', 'seo-fury' ); ?>
        </label>
        <?php
    }

    public function field_type() {
        $value = get_option( 'seo_fury_local_type', 'LocalBusiness' );
        ?>
        <input type="text" name="seo_fury_local_type" value="<?php echo esc_attr( $value ); ?>" class="regular-text" />
        <p class="description"><?php esc_html_e( 'Example: LocalBusiness, Store, Restaurant, MedicalBusiness', 'seo-fury' ); ?></p>
        <?php
    }

    public function field_text( $args ) {
        $key   = $args['key'];
        $value = get_option( $key, '' );
        ?>
        <input type="text" name="<?php echo esc_attr( $key ); ?>" value="<?php echo esc_attr( $value ); ?>" class="regular-text" />
        <?php
    }

    /**
     * Render the structured per-day opening hours table.
     *
     * Stored value is an array keyed by Schema.org day codes containing
     * 'open' and 'close' HH:MM strings.  Days absent from the array
     * are considered closed.
     */
    public function field_opening_hours() {
        $days  = $this->get_days();
        $saved = get_option( 'seo_fury_local_opening_hours', array() );

        // Backwards-compat: if the old textarea string is still stored, ignore it.
        if ( ! is_array( $saved ) ) {
            $saved = array();
        }

        echo '<table class="seo-fury-oh-table">';
        echo '<thead><tr>';
        echo '<th class="seo-fury-oh-day">'  . esc_html__( 'Day',          'seo-fury' ) . '</th>';
        echo '<th>'                            . esc_html__( 'Opening time', 'seo-fury' ) . '</th>';
        echo '<th></th>';
        echo '<th>'                            . esc_html__( 'Closing time', 'seo-fury' ) . '</th>';
        echo '</tr></thead>';
        echo '<tbody>';

        foreach ( $days as $code => $label ) {
            $open  = isset( $saved[ $code ]['open'] )  ? $saved[ $code ]['open']  : '';
            $close = isset( $saved[ $code ]['close'] ) ? $saved[ $code ]['close'] : '';

            $base = 'seo_fury_local_opening_hours[' . esc_attr( $code ) . ']';

            echo '<tr class="seo-fury-oh-row">';
            echo '<td class="seo-fury-oh-day"><strong>' . esc_html( $label ) . '</strong></td>';
            echo '<td><input type="text" inputmode="numeric" placeholder="HH:MM"'
                 . ' name="' . $base . '[open]"'
                 . ' value="' . esc_attr( $open ) . '"'
                 . ' class="seo-fury-oh-open" maxlength="5" autocomplete="off" /></td>';
            echo '<td><span class="seo-fury-oh-sep">&ndash;</span></td>';
            echo '<td><input type="text" inputmode="numeric" placeholder="HH:MM"'
                 . ' name="' . $base . '[close]"'
                 . ' value="' . esc_attr( $close ) . '"'
                 . ' class="seo-fury-oh-close" maxlength="5" autocomplete="off" /></td>';
            echo '</tr>';
        }

        echo '</tbody></table>';
        echo '<p class="description">'
             . esc_html__( 'Enter times in 24-hour format (HH:MM). Leave both fields empty to mark the day as closed. If you enter an opening time, a closing time is also required.', 'seo-fury' )
             . '</p>';
    }

    /**
     * Build the LocalBusiness schema data array (without @context).
     *
     * @return array|null Schema node array, or null when the feature is disabled
     *                    or the business name is empty.
     */
    private function build_schema_data() {
        if ( ! get_option( 'seo_fury_local_enabled', 0 ) ) {
            return null;
        }

        $type = get_option( 'seo_fury_local_type', 'LocalBusiness' );
        $data = array(
            '@type'      => $type,
            'name'       => get_option( 'seo_fury_local_name', '' ),
            'telephone'  => get_option( 'seo_fury_local_phone', '' ),
            'email'      => get_option( 'seo_fury_local_email', '' ),
            'url'        => get_option( 'seo_fury_local_url', home_url( '/' ) ),
            'image'      => get_option( 'seo_fury_local_image', '' ),
            'logo'       => get_option( 'seo_fury_local_logo', '' ),
            'priceRange' => get_option( 'seo_fury_local_price_range', '' ),
            'address'    => array(
                '@type'           => 'PostalAddress',
                'streetAddress'   => get_option( 'seo_fury_local_street', '' ),
                'addressLocality' => get_option( 'seo_fury_local_city', '' ),
                'addressRegion'   => get_option( 'seo_fury_local_region', '' ),
                'postalCode'      => get_option( 'seo_fury_local_postal', '' ),
                'addressCountry'  => get_option( 'seo_fury_local_country', '' ),
            ),
        );

        $lat = get_option( 'seo_fury_local_lat', '' );
        $lng = get_option( 'seo_fury_local_lng', '' );
        if ( $lat !== '' && $lng !== '' ) {
            $data['geo'] = array(
                '@type'     => 'GeoCoordinates',
                'latitude'  => $lat,
                'longitude' => $lng,
            );
        }

        // Build openingHours in Schema.org format ("Mo 09:00-17:00" per day).
        $this->apply_opening_hours( $data );

        // Remove empty scalar fields to keep the output clean.
        foreach ( array( 'telephone', 'email', 'image', 'logo', 'priceRange' ) as $field ) {
            if ( isset( $data[ $field ] ) && $data[ $field ] === '' ) {
                unset( $data[ $field ] );
            }
        }

        return $data;
    }

    /**
     * Parse stored opening hours and add openingHoursSpecification to the schema data.
     *
     * New structured format → openingHoursSpecification (Schema.org).
     * Days that share an identical open/close pair are grouped into a single
     * OpeningHoursSpecification node with an array of dayOfWeek values,
     * keeping the output as compact as possible.
     *
     * Legacy plain-text format (saved before the UI redesign) falls back to
     * the simpler openingHours string array so existing data is not lost.
     *
     * @param array $data Schema data array passed by reference.
     */
    private function apply_opening_hours( array &$data ) {
        $saved = get_option( 'seo_fury_local_opening_hours', array() );

        if ( is_array( $saved ) && ! empty( $saved ) ) {

            // Schema.org full day names keyed by our two-letter codes.
            $day_names = array(
                'Mo' => 'Monday',
                'Tu' => 'Tuesday',
                'We' => 'Wednesday',
                'Th' => 'Thursday',
                'Fr' => 'Friday',
                'Sa' => 'Saturday',
                'Su' => 'Sunday',
            );

            // Group days by identical time interval ("HH:MM|HH:MM" as key).
            // Iterating get_days() preserves Mo→Su order within each group.
            $groups = array();

            foreach ( array_keys( $this->get_days() ) as $code ) {
                if ( ! isset( $saved[ $code ] ) ) {
                    continue;
                }
                $open  = trim( $saved[ $code ]['open']  ?? '' );
                $close = trim( $saved[ $code ]['close'] ?? '' );

                if ( $open === '' || $close === '' ) {
                    continue;
                }

                $key = $open . '|' . $close;
                if ( ! isset( $groups[ $key ] ) ) {
                    $groups[ $key ] = array(
                        'days'  => array(),
                        'open'  => $open,
                        'close' => $close,
                    );
                }
                $groups[ $key ]['days'][] = $day_names[ $code ];
            }

            if ( ! empty( $groups ) ) {
                $specs = array();
                foreach ( $groups as $group ) {
                    $specs[] = array(
                        '@type'     => 'OpeningHoursSpecification',
                        'dayOfWeek' => $group['days'],
                        'opens'     => $group['open'],
                        'closes'    => $group['close'],
                    );
                }
                // A single specification node can be a plain object instead of a 1-element array.
                $data['openingHoursSpecification'] = count( $specs ) === 1 ? $specs[0] : $specs;
            }

        } elseif ( is_string( $saved ) && $saved !== '' ) {
            // Legacy textarea format — keep as openingHours strings (no structured data available).
            $lines = array_filter( array_map( 'trim', preg_split( '/\r\n|\r|\n/', $saved ) ) );
            if ( ! empty( $lines ) ) {
                $data['openingHours'] = array_values( $lines );
            }
        }
    }

    /**
     * Filter callback: inject the LocalBusiness node into the unified @graph
     * that SEO_Fury_Post_Meta_Schema builds on singular pages.
     *
     * @param array    $nodes Existing schema nodes collected so far.
     * @param \WP_Post $post  Current post object.
     * @return array
     */
    public function inject_schema_node( $nodes, $post ) {
        $node = $this->build_schema_data();
        if ( $node ) {
            $nodes[] = $node;
        }
        return $nodes;
    }

    /**
     * Output a standalone LocalBusiness block on non-singular pages
     * (archives, home, search, etc.) where the main schema graph is not built.
     * On singular pages the node is already injected via the seo_fury_schema_nodes
     * filter, so we skip the standalone output there.
     */
    public function output_schema() {
        if ( is_singular() ) {
            // Handled by inject_schema_node() → seo_fury_schema_nodes filter.
            return;
        }

        $data = $this->build_schema_data();
        if ( ! $data ) {
            return;
        }

        $data['@context'] = 'https://schema.org';

        $brand = function_exists( 'seo_fury_get_brand_name' ) ? seo_fury_get_brand_name() : 'SEO Fury';
        echo "\n<!-- " . esc_html( $brand ) . " -->\n";
        echo '<script type="application/ld+json">' . wp_json_encode( $data, JSON_HEX_TAG | JSON_HEX_AMP ) . '</script>' . "\n";
        echo "<!-- /" . esc_html( $brand ) . " -->\n";
    }
}
