<?php
/**
 * Eventit Events - appearance ("shortcode tilpasning") options.
 *
 * Single source of truth for the appearance settings:
 *  - schema + defaults            eventit_events_style_schema()
 *  - load (merged, migrated)      eventit_events_get_style_options()
 *  - sanitize a form submission   eventit_events_sanitize_style_options()
 *  - resolve theme presets        eventit_events_style_resolve()
 *  - CSS variables + classes      eventit_events_style_css() / eventit_events_style_classes()
 *
 * The frontend list and the admin live preview both go through these, so what
 * the admin sees is exactly what the shortcode renders.
 */
defined( 'ABSPATH' ) or die( 'I cannot do anything when called directly good sir' );

/* The accent colour used when neither the theme nor the user supplies one.
   This was the default value of the 'accent-color' setting before it became
   optional, so leaving the field empty keeps the previous appearance. */
if ( ! defined( 'EVENTIT_EVENTS_DEFAULT_ACCENT' ) ) {
    define( 'EVENTIT_EVENTS_DEFAULT_ACCENT', '#126666' );
}

/**
 * Option schema. Types:
 *  hex      - colour, required (empty falls back to default)
 *  hex-auto - colour, '' means "automatic" (follow colour theme)
 *  int      - integer within [min, max]
 *  select   - one of 'options' keys
 *  css      - free text CSS
 */
function eventit_events_style_schema() {
    static $schema = null;
    if ( $schema !== null ) {
        return $schema;
    }

    $schema = array(
        /* Colour theme */
        'color-theme' => array(
            'type' => 'select', 'default' => 1, 'label' => 'Farveudseende',
            'options' => array( 1 => 'Farve pr. kategori (fra Event.it)', 0 => 'Én farve for alle events', 3 => 'Mørkt tema' ),
        ),
        /* hex-auto: empty means "follow the colour theme". An explicit colour is
           respected on every theme, including the Eventit palette. */
        'accent-color' => array( 'type' => 'hex-auto', 'default' => '', 'label' => 'Hovedfarve' ),

        /* Card */
        'card-background'   => array( 'type' => 'hex-auto', 'default' => '', 'label' => 'Kortbaggrund' ),
        'card-border-color' => array( 'type' => 'hex-auto', 'default' => '', 'label' => 'Kantfarve' ),
        'card-border-width' => array(
            'type' => 'select', 'default' => 1, 'label' => 'Kanttykkelse',
            'options' => array( 0 => 'Ingen kant', 1 => '1 px', 2 => '2 px' ),
        ),
        'card-shadow' => array(
            'type' => 'select', 'default' => 'light', 'label' => 'Skygge',
            'options' => array( 'none' => 'Ingen', 'light' => 'Let', 'strong' => 'Tydelig' ),
        ),
        'card-hover'   => array( 'type' => 'select', 'default' => 1, 'label' => 'Løft ved hover', 'options' => array( 1 => 'Til', 0 => 'Fra' ) ),
        'card-radius'  => array( 'type' => 'int', 'default' => 0, 'min' => 0, 'max' => 24, 'label' => 'Hjørneradius' ),
        'card-spacing' => array(
            'type' => 'select', 'default' => 'normal', 'label' => 'Afstand mellem events',
            'options' => array( 'compact' => 'Kompakt', 'normal' => 'Normal', 'airy' => 'Luftig' ),
        ),
        'card-padding' => array(
            'type' => 'select', 'default' => 'normal', 'label' => 'Indvendig luft',
            'options' => array( 'compact' => 'Kompakt', 'normal' => 'Normal', 'airy' => 'Luftig' ),
        ),

        /* Date block */
        'date-style' => array(
            'type' => 'select', 'default' => 'block', 'label' => 'Datoblok',
            'options' => array( 'block' => 'Farvet blok', 'stripe' => 'Kun farvet stribe', 'plain' => 'Uden farve', 'circle' => 'Cirkel-badge' ),
        ),
        'date-background' => array( 'type' => 'hex-auto', 'default' => '', 'label' => 'Datoblok baggrund' ),
        'date-text-color' => array( 'type' => 'hex-auto', 'default' => '', 'label' => 'Datoblok tekstfarve' ),

        /* Category tags */
        'tag-show'  => array( 'type' => 'select', 'default' => 1, 'label' => 'Vis kategori-tags', 'options' => array( 1 => 'Vis', 0 => 'Skjul' ) ),
        'tag-shape' => array(
            'type' => 'select', 'default' => 'pill', 'label' => 'Tag-form',
            'options' => array( 'pill' => 'Pille', 'square' => 'Firkantet', 'dot' => 'Tekst med prik' ),
        ),
        'tag-color' => array( 'type' => 'hex-auto', 'default' => '', 'label' => 'Tag-farve' ),

        /* Typography */
        'title-size'   => array( 'type' => 'select', 'default' => 'm', 'label' => 'Titelstørrelse', 'options' => array( 's' => 'Lille', 'm' => 'Mellem', 'l' => 'Stor' ) ),
        'title-weight' => array( 'type' => 'select', 'default' => 'bold', 'label' => 'Titelvægt', 'options' => array( 'bold' => 'Fed', 'normal' => 'Normal' ) ),
        'title-color'  => array( 'type' => 'hex-auto', 'default' => '', 'label' => 'Titelfarve' ),
        'text-color'   => array( 'type' => 'hex-auto', 'default' => '', 'label' => 'Tekstfarve (beskrivelse/sted)' ),
        'desc-lines'   => array(
            'type' => 'select', 'default' => 'auto', 'label' => 'Beskrivelse',
            'options' => array( 'auto' => 'Standard (1 linje i liste, alt i box)', '1' => 'Maks. 1 linje', '2' => 'Maks. 2 linjer', 'all' => 'Vis hele teksten' ),
        ),
        'button-uppercase' => array( 'type' => 'select', 'default' => 1, 'label' => 'Knaptekst med versaler', 'options' => array( 1 => 'VERSALER', 0 => 'Normal tekst' ) ),

        /* Buttons */
        'button-style' => array(
            'type' => 'select', 'default' => 'filled', 'label' => 'Knapstil',
            'options' => array( 'filled' => 'Udfyldt', 'outline' => 'Outline', 'link' => 'Link' ),
        ),
        'button-size'   => array( 'type' => 'select', 'default' => 'm', 'label' => 'Knapstørrelse', 'options' => array( 's' => 'Lille', 'm' => 'Mellem', 'l' => 'Stor' ) ),
        'button-radius' => array( 'type' => 'int', 'default' => 8, 'min' => 0, 'max' => 30, 'label' => 'Knap-hjørner' ),
        'button-hover'  => array(
            'type' => 'select', 'default' => 'darken', 'label' => 'Hover-effekt',
            'options' => array( 'darken' => 'Mørkere', 'lighten' => 'Lysere', 'none' => 'Ingen' ),
        ),
        'button-info-background' => array( 'type' => 'hex', 'default' => '#effeff', 'label' => 'Info-knap baggrundsfarve' ),
        'button-info-font-color' => array( 'type' => 'hex', 'default' => '#262628', 'label' => 'Info-knap tekstfarve' ),
        'button-buy-background'  => array( 'type' => 'hex', 'default' => '#effeff', 'label' => 'Køb-knap baggrundsfarve' ),
        'button-buy-font-color'  => array( 'type' => 'hex', 'default' => '#262628', 'label' => 'Køb-knap tekstfarve' ),
        'button-waitlist-background' => array( 'type' => 'hex-auto', 'default' => '', 'label' => 'Venteliste-knap baggrundsfarve' ),
        'button-waitlist-font-color' => array( 'type' => 'hex-auto', 'default' => '', 'label' => 'Venteliste-knap tekstfarve' ),

        /* Boxed view */
        'boxed-columns' => array( 'type' => 'select', 'default' => 3, 'label' => 'Kolonner i box-visning', 'options' => array( 2 => '2 kolonner', 3 => '3 kolonner', 4 => '4 kolonner' ) ),

        /* Advanced */
        'custom-css' => array( 'type' => 'css', 'default' => '', 'label' => 'Egen CSS' ),
    );

    return $schema;
}

/** Default values only (key => default). */
function eventit_events_style_defaults() {
    $defaults = array();
    foreach ( eventit_events_style_schema() as $key => $field ) {
        $defaults[ $key ] = $field['default'];
    }
    return $defaults;
}

/**
 * Merge stored values with defaults and migrate legacy keys.
 * Always returns a complete, valid options array.
 */
function eventit_events_get_style_options() {
    $stored = get_option( 'eventit_events_shortcode_options' );
    if ( ! is_array( $stored ) ) {
        $stored = array();
    }

    /* Legacy: border-radius was a 0/1 toggle (sharp / rounded) */
    if ( ! isset( $stored['card-radius'] ) && isset( $stored['border-radius'] ) ) {
        $stored['card-radius'] = intval( $stored['border-radius'] ) === 1 ? 12 : 0;
    }

    /* Legacy: 'accent-color' was a required colour that defaulted to #126666 and was
       ignored by the Eventit and Neutral themes. Now an explicit colour wins on every
       theme, so a stored value that is only the old untouched default must not suddenly
       start overriding those two themes. On themes 2 and 3 the value was already in
       use, so it is kept as the deliberate choice it was. */
    if ( isset( $stored['accent-color'] ) && strtolower( (string) $stored['accent-color'] ) === EVENTIT_EVENTS_DEFAULT_ACCENT ) {
        $storedTheme = isset( $stored['color-theme'] ) ? intval( $stored['color-theme'] ) : 1;
        if ( $storedTheme === 0 || $storedTheme === 1 ) {
            $stored['accent-color'] = '';
        }
    }

    /* Legacy: theme 2 ("Egen accentfarve") merged into theme 0 ("Én farve for alle
       events"). They only ever differed in the card background, and once a chosen
       colour won on every theme they rendered identically.

       This MUST run after the accent migration above: that one keys off the stored
       theme, and on theme 2 a stored #126666 was a deliberate choice that has to be
       kept. Rewriting the theme first would make it look like theme 0 and wipe the
       colour. */
    if ( isset( $stored['color-theme'] ) && intval( $stored['color-theme'] ) === 2 ) {
        /* Theme 2 had no colour of its own, so an empty field there resolved to the
           historic default rather than to grey. The merged theme is grey when empty,
           so the colour has to be written out explicitly to keep the appearance. */
        if ( ! isset( $stored['accent-color'] ) || trim( (string) $stored['accent-color'] ) === '' ) {
            $stored['accent-color'] = EVENTIT_EVENTS_DEFAULT_ACCENT;
        }
        $stored['color-theme'] = 0;
    }

    $clean = eventit_events_sanitize_style_options( $stored, true );
    return $clean['options'];
}

/**
 * Sanitize a raw options array against the schema.
 *
 * @param array $raw     Raw values (e.g. $_POST[...] or stored option).
 * @param bool  $lenient When true, invalid values silently fall back to the default
 *                       (used when loading). When false, they are reported in 'errors'.
 * @return array { options: array, errors: string[] }
 */
function eventit_events_sanitize_style_options( $raw, $lenient = false ) {
    $options = array();
    $errors  = array();

    foreach ( eventit_events_style_schema() as $key => $field ) {
        $value = isset( $raw[ $key ] ) ? $raw[ $key ] : null;
        if ( is_string( $value ) ) {
            $value = trim( wp_unslash( $value ) );
        }

        switch ( $field['type'] ) {
            case 'hex':
            case 'hex-auto':
                if ( $value === null || $value === '' ) {
                    $options[ $key ] = $field['type'] === 'hex-auto' ? '' : $field['default'];
                    break;
                }
                $hex = sanitize_hex_color( $value );
                if ( ! $hex ) {
                    $options[ $key ] = $field['default'];
                    if ( ! $lenient ) {
                        $errors[] = $field['label'] . ' er ikke en gyldig farve. Vælg igen.';
                    }
                } else {
                    $options[ $key ] = strtolower( $hex );
                }
                break;

            case 'int':
                if ( $value === null || $value === '' || ! is_numeric( $value ) ) {
                    $options[ $key ] = $field['default'];
                    if ( ! $lenient && $value !== null && $value !== '' ) {
                        $errors[] = $field['label'] . ' skal være et tal.';
                    }
                    break;
                }
                $options[ $key ] = max( $field['min'], min( $field['max'], intval( $value ) ) );
                break;

            case 'select':
                /* Compare as strings so 0 / '0' / 'none' all behave */
                $allowed = array_map( 'strval', array_keys( $field['options'] ) );
                if ( $value === null || ! in_array( strval( $value ), $allowed, true ) ) {
                    $options[ $key ] = $field['default'];
                    if ( ! $lenient && $value !== null && $value !== '' ) {
                        $errors[] = $field['label'] . ' har en ugyldig værdi. Vælg igen.';
                    }
                } else {
                    /* Keep the key's native type (int keys stay ints) */
                    foreach ( array_keys( $field['options'] ) as $optKey ) {
                        if ( strval( $optKey ) === strval( $value ) ) {
                            $options[ $key ] = $optKey;
                            break;
                        }
                    }
                }
                break;

            case 'css':
                $css = is_string( $value ) ? $value : '';
                $css = wp_strip_all_tags( $css );
                /* Never allow the style block to be closed from inside the CSS.
                   Repeat until stable: a single pass can re-form a tag from the
                   remains of an overlapping one ("</st</styleyle>"). */
                do {
                    $before = $css;
                    $css    = preg_replace( '#</?\s*style#i', '', $css );
                } while ( $css !== $before );
                $options[ $key ] = $css;
                break;
        }
    }

    return array( 'options' => $options, 'errors' => $errors );
}

/* ---------------------------------------------------------------------------
 * Colour helpers
 * ------------------------------------------------------------------------- */

/** '#abc' / '#aabbcc' -> [r,g,b] or null */
function eventit_events_hex_to_rgb( $hex ) {
    $hex = ltrim( (string) $hex, '#' );
    if ( strlen( $hex ) === 3 ) {
        $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
    }
    if ( strlen( $hex ) !== 6 || ! ctype_xdigit( $hex ) ) {
        return null;
    }
    return array( hexdec( substr( $hex, 0, 2 ) ), hexdec( substr( $hex, 2, 2 ) ), hexdec( substr( $hex, 4, 2 ) ) );
}

/** Mix colour A into colour B. $weightB = share of B (0..1). Returns '#rrggbb'. */
function eventit_events_mix_hex( $hexA, $hexB, $weightB ) {
    $a = eventit_events_hex_to_rgb( $hexA );
    $b = eventit_events_hex_to_rgb( $hexB );
    if ( ! $a || ! $b ) {
        return $hexA;
    }
    $weightB = max( 0, min( 1, floatval( $weightB ) ) );
    $out = '#';
    for ( $i = 0; $i < 3; $i++ ) {
        $c = round( $a[ $i ] * ( 1 - $weightB ) + $b[ $i ] * $weightB );
        $out .= str_pad( dechex( (int) $c ), 2, '0', STR_PAD_LEFT );
    }
    return $out;
}

/* ---------------------------------------------------------------------------
 * Resolution: options -> concrete values (theme presets + user overrides)
 * ------------------------------------------------------------------------- */

/** Theme presets. Keys with '' are filled from user options in resolve(). */
function eventit_events_style_theme_preset( $theme ) {
    $presets = array(
        /* "Én farve for alle events": grey until a main colour is picked. Merged from
           the former "Neutral" and "Egen accentfarve" - those differed only in the
           card background, and once a chosen colour started winning on every theme
           they rendered the same. */
        0 => array( 'accent' => '#bbbbbb', 'soft' => '#eaeaea', 'card-bg' => '#ffffff', 'text' => '#1f2933', 'muted' => '#5b6470', 'border' => '#e4e7ec', 'soft-mix' => null ),
        1 => array( 'accent' => '#126666', 'soft' => '#e0eaea', 'card-bg' => '#f7fbfe', 'text' => '#1f2933', 'muted' => '#5b6470', 'border' => '#e4e7ec', 'soft-mix' => null ),
        /* Kept so a stored 2 that somehow escapes the migration still resolves. */
        2 => array( 'accent' => '',        'soft' => '',        'card-bg' => '#ffffff', 'text' => '#1f2933', 'muted' => '#5b6470', 'border' => '#e4e7ec', 'soft-mix' => 0.84 ),
        3 => array( 'accent' => '',        'soft' => '',        'card-bg' => '#1f2430', 'text' => '#f1f3f5', 'muted' => '#aab2bd', 'border' => '#353c4a', 'soft-mix' => 0.72 ),
    );
    return isset( $presets[ $theme ] ) ? $presets[ $theme ] : $presets[1];
}

/**
 * Turn the options into the concrete CSS variable values.
 * Returns array( 'vars' => [ '--eventit-x' => 'value', ... ], 'classes' => [...] ).
 */
function eventit_events_style_resolve( $o ) {
    $theme  = intval( $o['color-theme'] );
    $preset = eventit_events_style_theme_preset( $theme );

    $cardBg = $o['card-background'] !== '' ? $o['card-background'] : $preset['card-bg'];

    /* An explicit main colour wins - except with the per-category palette, where the
       categories own the colour and the field is hidden in admin. Honouring a stored
       value there would let a colour picked under another theme leak back in through
       events that have no category, which is invisible from the settings screen.
       On that theme the preset resolves to the same values the categories use, so
       index 0 and the old hard-coded index 1 render identically. */
    $accentChosen = $o['accent-color'] !== '' && $theme !== 1;
    $accent = $accentChosen
        ? $o['accent-color']
        : ( $preset['accent'] !== '' ? $preset['accent'] : EVENTIT_EVENTS_DEFAULT_ACCENT );

    /* The soft tint has to follow a chosen accent, otherwise a custom colour would
       sit next to the theme's own pale shade. Themes without a mix of their own use
       the same ratio as the merged one-colour theme. */
    if ( $accentChosen || $preset['soft'] === '' ) {
        $softMix = $preset['soft-mix'] !== null ? $preset['soft-mix'] : 0.84;
        $soft    = eventit_events_mix_hex( $accent, $cardBg, $softMix );
    } else {
        $soft = $preset['soft'];
    }
    $text   = $preset['text'];
    $muted  = $o['text-color'] !== '' ? $o['text-color'] : $preset['muted'];
    $title  = $o['title-color'] !== '' ? $o['title-color'] : $text;
    $border = $o['card-border-color'] !== '' ? $o['card-border-color'] : $preset['border'];

    $shadows = array(
        'none'   => array( 'none', 'none' ),
        'light'  => array( '0 1px 2px rgba(16, 24, 40, .06)', '0 6px 16px rgba(16, 24, 40, .10)' ),
        'strong' => array( '0 4px 12px rgba(16, 24, 40, .12)', '0 12px 28px rgba(16, 24, 40, .18)' ),
    );
    $shadow = isset( $shadows[ $o['card-shadow'] ] ) ? $shadows[ $o['card-shadow'] ] : $shadows['light'];
    $hover  = intval( $o['card-hover'] ) === 1;

    $gaps = array( 'compact' => '8px', 'normal' => '16px', 'airy' => '28px' );
    $pads = array( 'compact' => '12px', 'normal' => '20px', 'airy' => '28px' );
    $titleSizes = array( 's' => '16px', 'm' => '19px', 'l' => '22px' );
    $btnPads  = array( 's' => '5px 10px', 'm' => '8px 14px', 'l' => '11px 20px' );
    $btnFonts = array( 's' => '12px', 'm' => '14px', 'l' => '16px' );

    $vars = array(
        '--eventit-accent'       => $accent,
        '--eventit-accent-soft'  => $soft,
        '--eventit-card-bg'      => $cardBg,
        '--eventit-text'         => $text,
        '--eventit-muted'        => $muted,
        '--eventit-title-color'  => $title,
        '--eventit-border'       => $border,
        '--eventit-border-width' => intval( $o['card-border-width'] ) . 'px',
        '--eventit-radius'       => intval( $o['card-radius'] ) . 'px',
        '--eventit-shadow'       => $shadow[0],
        '--eventit-shadow-hover' => $hover ? $shadow[1] : $shadow[0],
        '--eventit-hover-lift'   => $hover ? '-1px' : '0px',
        '--eventit-gap'          => isset( $gaps[ $o['card-spacing'] ] ) ? $gaps[ $o['card-spacing'] ] : $gaps['normal'],
        '--eventit-pad'          => isset( $pads[ $o['card-padding'] ] ) ? $pads[ $o['card-padding'] ] : $pads['normal'],
        '--eventit-title-size'   => isset( $titleSizes[ $o['title-size'] ] ) ? $titleSizes[ $o['title-size'] ] : $titleSizes['m'],
        '--eventit-title-weight' => $o['title-weight'] === 'normal' ? '500' : '700',
        '--eventit-btn-radius'   => intval( $o['button-radius'] ) . 'px',
        '--eventit-btn-pad'      => isset( $btnPads[ $o['button-size'] ] ) ? $btnPads[ $o['button-size'] ] : $btnPads['m'],
        '--eventit-btn-font'     => isset( $btnFonts[ $o['button-size'] ] ) ? $btnFonts[ $o['button-size'] ] : $btnFonts['m'],
        '--eventit-btn-info-bg'  => $o['button-info-background'],
        '--eventit-btn-info-fg'  => $o['button-info-font-color'],
        '--eventit-btn-buy-bg'   => $o['button-buy-background'],
        '--eventit-btn-buy-fg'   => $o['button-buy-font-color'],
        /* Waiting-list button falls back to the buy-button colours */
        '--eventit-btn-wait-bg'  => $o['button-waitlist-background'] !== '' ? $o['button-waitlist-background'] : $o['button-buy-background'],
        '--eventit-btn-wait-fg'  => $o['button-waitlist-font-color'] !== '' ? $o['button-waitlist-font-color'] : $o['button-buy-font-color'],
    );

    /* Explicit card border colour must also win over the per-category soft border in list view */
    if ( $o['card-border-color'] !== '' ) {
        $vars['--eventit-border-custom'] = $o['card-border-color'];
    }

    /* Date + tag colours: empty follows the theme (and, with the Eventit palette,
       the category's own colour) - an explicit colour always wins, on every theme.
       No guard is needed: the var() chains in flex.css read the -custom variables
       before the per-category ones, so simply not defining them is the fallback.
         background: var(--eventit-date-bg-custom, var(--eventit-date-bg, ...)) */
    if ( $o['date-background'] !== '' ) {
        $vars['--eventit-date-bg-custom'] = $o['date-background'];
    }
    if ( $o['date-text-color'] !== '' ) {
        $vars['--eventit-date-fg-custom'] = $o['date-text-color'];
    }
    if ( $o['tag-color'] !== '' ) {
        $vars['--eventit-tag-bg-custom'] = $o['tag-color'];
    }

    $classes = array(
        'eventit-bootstrap',
        'eventit-theme-' . $theme,
        'eventit-date-' . $o['date-style'],
        'eventit-tag-' . $o['tag-shape'],
        'eventit-btn-' . $o['button-style'],
        'eventit-btn-hover-' . $o['button-hover'],
        'eventit-desc-' . $o['desc-lines'],
    );
    if ( intval( $o['button-uppercase'] ) === 1 ) {
        $classes[] = 'eventit-btn-upper';
    }
    if ( intval( $o['tag-show'] ) !== 1 ) {
        $classes[] = 'eventit-tags-hidden';
    }

    return array( 'vars' => $vars, 'classes' => $classes );
}

/** Space separated class list for the #eventdetaillist container. */
function eventit_events_style_classes( $o ) {
    $resolved = eventit_events_style_resolve( $o );
    return implode( ' ', $resolved['classes'] );
}

/**
 * The generated CSS: variable block on #eventdetaillist + the user's custom CSS.
 * Printed after flex.css so it overrides the defaults defined there.
 */
function eventit_events_style_css( $o ) {
    $resolved = eventit_events_style_resolve( $o );

    $css = "#eventdetaillist {\n";
    foreach ( $resolved['vars'] as $name => $value ) {
        $css .= '    ' . $name . ': ' . $value . ";\n";
    }
    $css .= "}\n";

    if ( trim( (string) $o['custom-css'] ) !== '' ) {
        $css .= "/* Egen CSS */\n" . $o['custom-css'] . "\n";
    }

    return $css;
}
