<?php
if ( ! defined( 'ABSPATH' ) ) { exit; }

/**
 * Utility helpers: HMAC token generation/validation, file utilities.
 */
class TNC_Flipbook_3D_Helpers {

    const TOKEN_TTL = 2 * HOUR_IN_SECONDS; // 2 hours

    /**
     * Render the SVG schematic for a toolbar-layout picker card.
     * Echoes inline SVG. Bar position depends on the layout key.
     *
     * @param string $layout bottom-center|top-center|bottom-fullwidth|top-fullwidth
     */
    public static function render_toolbar_layout_svg( $layout ) {
        switch ( $layout ) {
            case 'top-center':
                $bar = '<rect class="tncfb-lc-bar" x="45" y="6" width="40" height="8" rx="4" />';
                break;
            case 'bottom-fullwidth':
                $bar = '<rect class="tncfb-lc-bar" x="8" y="66" width="114" height="8" rx="2" />';
                break;
            case 'top-fullwidth':
                $bar = '<rect class="tncfb-lc-bar" x="8" y="6" width="114" height="8" rx="2" />';
                break;
            case 'bottom-center':
            default:
                $bar = '<rect class="tncfb-lc-bar" x="45" y="66" width="40" height="8" rx="4" />';
                break;
        }
        $lines = '<rect class="tncfb-lc-page-line" x="14" y="30" width="80" height="3" rx="1.5" />'
               . '<rect class="tncfb-lc-page-line" x="14" y="40" width="95" height="3" rx="1.5" />'
               . '<rect class="tncfb-lc-page-line" x="14" y="50" width="65" height="3" rx="1.5" />';
        // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped -- Static SVG markup composed from internal literals; no user input.
        echo '<svg class="tncfb-layout-card-svg" viewBox="0 0 130 80" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">'
            . $lines . $bar
            . '</svg>';
        // phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped
    }

    /**
     * Render an inline SVG illustrating a default view mode layout.
     *
     * @param string $mode flipbook|single|vertical|horizontal
     */
    public static function render_view_mode_svg( $mode ) {
        switch ( $mode ) {
            case 'single':
                $shapes = '<rect class="tncfb-lc-bar" x="44" y="14" width="42" height="52" rx="2" />'
                        . '<rect x="50" y="24" width="30" height="2.5" rx="1.25" fill="#fff" />'
                        . '<rect x="50" y="32" width="34" height="2.5" rx="1.25" fill="#fff" />'
                        . '<rect x="50" y="40" width="22" height="2.5" rx="1.25" fill="#fff" />';
                break;
            case 'vertical':
                $shapes = '<rect class="tncfb-lc-bar" x="44" y="6"  width="42" height="20" rx="2" />'
                        . '<rect class="tncfb-lc-bar" x="44" y="30" width="42" height="20" rx="2" />'
                        . '<rect class="tncfb-lc-bar" x="44" y="54" width="42" height="20" rx="2" />';
                break;
            case 'horizontal':
                $shapes = '<rect class="tncfb-lc-bar" x="6"  y="22" width="36" height="36" rx="2" />'
                        . '<rect class="tncfb-lc-bar" x="47" y="22" width="36" height="36" rx="2" />'
                        . '<rect class="tncfb-lc-bar" x="88" y="22" width="36" height="36" rx="2" />';
                break;
            case 'flipbook':
            default:
                $shapes = '<rect class="tncfb-lc-bar" x="20" y="14" width="42" height="52" rx="2" />'
                        . '<rect class="tncfb-lc-bar" x="68" y="14" width="42" height="52" rx="2" />'
                        . '<rect x="26" y="24" width="30" height="2.5" rx="1.25" fill="#fff" />'
                        . '<rect x="26" y="32" width="34" height="2.5" rx="1.25" fill="#fff" />'
                        . '<rect x="26" y="40" width="22" height="2.5" rx="1.25" fill="#fff" />'
                        . '<rect x="74" y="24" width="30" height="2.5" rx="1.25" fill="#fff" />'
                        . '<rect x="74" y="32" width="34" height="2.5" rx="1.25" fill="#fff" />'
                        . '<rect x="74" y="40" width="22" height="2.5" rx="1.25" fill="#fff" />';
                break;
        }
        // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped -- Static SVG markup composed from internal literals; no user input.
        echo '<svg class="tncfb-layout-card-svg" viewBox="0 0 130 80" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">'
            . $shapes
            . '</svg>';
        // phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped
    }

    /**
     * Generate a signed proxy token for a file request.
     *
     * @param int    $flipbook_id
     * @param string $file_type   pdf|image|video|media|audio
     * @param int    $index
     * @return array { token: string, exp: int }
     */
    public static function generate_token( $flipbook_id, $file_type, $index ) {
        $exp     = time() + self::TOKEN_TTL;
        $message = absint( $flipbook_id ) . '|' . sanitize_key( $file_type ) . '|' . absint( $index ) . '|' . $exp;
        $token   = hash_hmac( 'sha256', $message, wp_salt( 'nonce' ) );
        return array( 'token' => $token, 'exp' => $exp );
    }

    /**
     * Validate a proxy token.
     *
     * @param int    $flipbook_id
     * @param string $file_type
     * @param int    $index
     * @param string $token
     * @param int    $exp
     * @return bool
     */
    public static function verify_token( $flipbook_id, $file_type, $index, $token, $exp ) {
        if ( $exp <= time() ) {
            return false; // Expired
        }
        $message  = absint( $flipbook_id ) . '|' . sanitize_key( $file_type ) . '|' . absint( $index ) . '|' . absint( $exp );
        $expected = hash_hmac( 'sha256', $message, wp_salt( 'nonce' ) );
        return hash_equals( $expected, $token );
    }

    /**
     * Build a complete proxy URL for a file.
     *
     * @param int    $flipbook_id
     * @param string $file_type
     * @param int    $index
     * @return string
     */
    public static function proxy_url( $flipbook_id, $file_type, $index ) {
        $t = self::generate_token( $flipbook_id, $file_type, $index );
        return add_query_arg( array(
            'file'  => rawurlencode( $file_type ),
            'index' => absint( $index ),
            'token' => rawurlencode( $t['token'] ),
            'exp'   => $t['exp'],
        ), rest_url( 'tnc-flipbook-3d/v1/serve/' . absint( $flipbook_id ) ) );
    }

    /**
     * Allowed MIME types for proxied files.
     */
    public static function allowed_mime_types() {
        return array(
            'application/pdf',
            'image/jpeg',
            'image/png',
            'image/gif',
            'image/webp',
            'image/svg+xml',
            'video/mp4',
            'video/webm',
            'video/ogg',
            'video/quicktime',
            'video/x-m4v',
            'video/avi',
            'video/x-msvideo',
            'video/x-ms-wmv',
            'audio/mpeg',
            'audio/ogg',
            'audio/wav',
            'audio/x-m4a',
            'audio/mp4',
        );
    }

    /**
     * Validate that a file path is within the WP uploads directory.
     *
     * @param string $path  Absolute server path.
     * @return bool
     */
    public static function path_is_in_uploads( $path ) {
        $upload = wp_get_upload_dir();
        $base   = realpath( $upload['basedir'] );
        $real   = realpath( $path );
        if ( ! $base || ! $real ) {
            return false;
        }
        return strpos( $real, $base . DIRECTORY_SEPARATOR ) === 0 || $real === $base;
    }

    /**
     * Validate that a URL is safe to fetch from a server-side context.
     * Rejects non-http(s) schemes, private/loopback/link-local IPs, and
     * unresolvable hosts. Returns true on success or WP_Error on failure.
     *
     * @param  string $url
     * @return true|WP_Error
     */
    public static function validate_remote_url( $url ) {
        if ( ! is_string( $url ) || '' === $url ) {
            return new WP_Error( 'tncfb3d_invalid_url', __( 'URL is empty.', 'tnc-flipbook-3d-lite' ) );
        }

        $parts = wp_parse_url( $url );
        if ( empty( $parts['scheme'] ) || empty( $parts['host'] ) ) {
            return new WP_Error( 'tncfb3d_invalid_url', __( 'URL is malformed.', 'tnc-flipbook-3d-lite' ) );
        }

        if ( ! in_array( strtolower( $parts['scheme'] ), array( 'http', 'https' ), true ) ) {
            return new WP_Error( 'tncfb3d_invalid_url', __( 'Only http(s) URLs are allowed.', 'tnc-flipbook-3d-lite' ) );
        }

        $host = $parts['host'];
        $ip   = filter_var( $host, FILTER_VALIDATE_IP ) ? $host : gethostbyname( $host );

        // gethostbyname returns the input string on failure — treat that as unresolvable.
        if ( $ip === $host && ! filter_var( $host, FILTER_VALIDATE_IP ) ) {
            return new WP_Error( 'tncfb3d_unresolvable_host', __( 'Host could not be resolved.', 'tnc-flipbook-3d-lite' ) );
        }

        if ( ! filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) ) {
            return new WP_Error( 'tncfb3d_blocked_host', __( 'URLs pointing at private or loopback addresses are not allowed.', 'tnc-flipbook-3d-lite' ) );
        }

        return true;
    }

    /**
     * Sanitize a CSS dimension value (e.g. "100%", "600px", "80vh").
     */
    public static function sanitize_dimension( $value ) {
        $value = sanitize_text_field( $value );
        if ( preg_match( '/^\d+(\.\d+)?(px|%|vh|vw|em|rem)$/', $value ) ) {
            return $value;
        }
        return '';
    }

    /**
     * Sanitize a hex colour value.
     */
    public static function sanitize_hex_color( $value ) {
        $value = sanitize_text_field( $value );
        return preg_match( '/^#[0-9a-fA-F]{3,8}$/', $value ) ? $value : '';
    }

    /**
     * Resolve initial chrome page background + polarity for the viewer's first paint.
     * Mirrors the chrome map in src/tnc-flipbook.js so we can avoid the
     * default-dark flash before applyColorScheme() runs.
     *
     * @param string $chrome     Chrome key (slate|paper|midnight|ocean|forest|glass|sepia)
     * @param string $color_mode auto|dark|light
     * @return array { mode: 'dark'|'light', pageBg: '#hex' }
     */
    public static function resolve_initial_chrome( $chrome, $color_mode ) {
        $chromes = array(
            'slate'    => array( 'defaultMode' => 'dark',  'dark' => '#0f0f12', 'light' => '#f5f5f7' ),
            'paper'    => array( 'defaultMode' => 'light', 'dark' => '#0f0f12', 'light' => '#f5f5f7' ),
            'midnight' => array( 'defaultMode' => 'dark',  'dark' => '#0a0e1a', 'light' => '#eef2fb' ),
            'ocean'    => array( 'defaultMode' => 'dark',  'dark' => '#0a1518', 'light' => '#eaf4f6' ),
            'forest'   => array( 'defaultMode' => 'dark',  'dark' => '#0c1410', 'light' => '#ecf3ee' ),
            'glass'    => array( 'defaultMode' => 'dark',  'dark' => '#0f0f12', 'light' => '#f5f5f7' ),
            'sepia'    => array( 'defaultMode' => 'light', 'dark' => '#1f1812', 'light' => '#f4ecd8' ),
        );

        // Legacy alias migration ('dark'→slate, 'light'→paper)
        if ( 'dark' === $chrome )  { $chrome = 'slate'; }
        if ( 'light' === $chrome ) { $chrome = 'paper'; }

        $entry = $chromes[ $chrome ] ?? $chromes['slate'];

        $mode = $color_mode;
        if ( 'auto' === $mode || ! in_array( $mode, array( 'dark', 'light' ), true ) ) {
            $mode = $entry['defaultMode'];
        }

        return array( 'mode' => $mode, 'pageBg' => $entry[ $mode ] );
    }

    /**
     * Deep sanitize an array of scalar values.
     */
    public static function sanitize_array_of_scalars( $arr ) {
        if ( ! is_array( $arr ) ) {
            return array();
        }
        return array_map( 'sanitize_text_field', $arr );
    }

    /**
     * Get the plugin asset URL.
     */
    public static function asset_url( $path ) {
        return TNC_FLIPBOOK_3D_URL . ltrim( $path, '/' );
    }

    /**
     * Get the list of supported languages grouped by region.
     *
     * @return array Grouped array: group_label => [ code => native_name, ... ]
     */
    public static function get_languages() {
        $groups = array(
            __( 'Popular', 'tnc-flipbook-3d-lite' ) => array(
                'en' => 'English', 'es' => 'Español', 'fr' => 'Français', 'de' => 'Deutsch',
                'it' => 'Italiano', 'pt' => 'Português (BR)', 'nl' => 'Nederlands', 'ru' => 'Русский',
                'zh' => '中文 (简体)', 'zh-TW' => '中文 (繁體)', 'ja' => '日本語', 'ko' => '한국어',
                'ar' => 'العربية', 'hi' => 'हिन्दी', 'tr' => 'Türkçe', 'pl' => 'Polski',
                'sv' => 'Svenska', 'da' => 'Dansk', 'no' => 'Norsk', 'fi' => 'Suomi',
            ),
        );

        return $groups;
    }

    /**
     * Total number of UI languages available in this build.
     * Counts the (gated) language registry, so it reports the real number
     * shipped: Pro returns all languages, Lite returns only the free set.
     *
     * @return int
     */
    public static function count_languages() {
        $count = 0;
        foreach ( self::get_languages() as $group ) {
            $count += count( $group );
        }
        return $count;
    }
}
