<?php
/**
 * Design System CSS generator.
 *
 * @package Polanger_Dashboard_Tools
 */

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

/**
 * Generates and caches CSS.
 */
class PDT_Design_Generator {
  /**
   * Settings service.
   *
   * @var PDT_Design_Settings
   */
  private $settings;

  /**
   * Constructor.
   *
   * @param PDT_Design_Settings $settings Settings service.
   */
  public function __construct( $settings ) {
    $this->settings = $settings;
  }

  /**
   * Resolve derived tokens before CSS compilation.
   *
   * @param array $tokens Raw tokens.
   * @return array
   */
  public function resolve_tokens( $tokens ) {
    $defaults = $this->settings->get_defaults();
    $tokens = is_array( $tokens ) ? array_merge( $defaults, $tokens ) : $defaults;

    // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Public addon extension hook.
    $tokens = apply_filters( 'pdt_design_tokens_before_compile', $tokens, $this->settings );

    $radius = isset( $tokens['border_radius'] ) ? absint( $tokens['border_radius'] ) : absint( $defaults['border_radius'] );
    $radius = min( 24, max( 0, $radius ) );

    $type_scale = $this->resolve_numeric_option( isset( $tokens['type_scale'] ) ? $tokens['type_scale'] : '100', array( 95, 100, 105, 110 ), 100 );
    $spacing_scale = $this->resolve_numeric_option( isset( $tokens['spacing_scale'] ) ? $tokens['spacing_scale'] : '100', array( 90, 100, 115 ), 100 );

    $density_map = array(
      'compact'     => 0.9,
      'cozy'        => 1.0,
      'comfortable' => 1.12,
    );
    $density = isset( $tokens['density'] ) && isset( $density_map[ $tokens['density'] ] ) ? $tokens['density'] : 'cozy';
    $density_scale = $density_map[ $density ];
    $space_scale = ( $spacing_scale / 100 ) * $density_scale;

    $primary = $this->normalize_hex( $tokens['color_primary'], $defaults['color_primary'] );
    $bg = $this->normalize_hex( $tokens['color_bg'], $defaults['color_bg'] );
    $surface = $this->normalize_hex( $tokens['color_surface'], $defaults['color_surface'] );
    $text = $this->normalize_hex( $tokens['color_text'], $defaults['color_text'] );
    $muted = $this->normalize_hex( $tokens['color_muted'], $defaults['color_muted'] );
    $sidebar_bg = $this->normalize_hex( $tokens['sidebar_bg'], $defaults['sidebar_bg'] );
    $sidebar_text = $this->normalize_hex( $tokens['sidebar_text'], $defaults['sidebar_text'] );
    $adminbar_bg = $this->normalize_hex( $tokens['adminbar_bg'], $defaults['adminbar_bg'] );
    $adminbar_text = $this->normalize_hex( $tokens['adminbar_text'], $defaults['adminbar_text'] );
    $adminbar_submenu_bg = $this->normalize_hex( $tokens['adminbar_submenu_bg'], $defaults['adminbar_submenu_bg'] );
    $adminbar_submenu_text = $this->normalize_hex( $tokens['adminbar_submenu_text'], $defaults['adminbar_submenu_text'] );
    $border = $this->normalize_hex( $tokens['color_border'], $this->mix_hex( $surface, $text, 14 ) );
    $hover = $this->normalize_hex( $tokens['color_hover'], $this->mix_hex( $surface, $primary, 10 ) );
    $focus = $this->normalize_hex( $tokens['color_focus'], $primary );
    $success = $this->normalize_hex( $tokens['color_success'], $defaults['color_success'] );
    $warning = $this->normalize_hex( $tokens['color_warning'], $defaults['color_warning'] );
    $error = $this->normalize_hex( $tokens['color_error'], $defaults['color_error'] );
    $table_head = $this->normalize_hex( $tokens['color_table_head'], $this->mix_hex( $surface, $primary, 8 ) );
    $input_bg = $this->normalize_hex( $tokens['color_input_bg'], $surface );

    $surface_hover = $this->normalize_hex( $this->derive_hover_color( $surface, 6 ), $hover );
    $sidebar_hover = $this->normalize_hex( $this->derive_hover_color( $sidebar_bg, 12 ), $hover );
    $sidebar_submenu = $this->normalize_hex( $this->derive_hover_color( $sidebar_bg, 8 ), $sidebar_bg );
    $sidebar_submenu_hover = $this->normalize_hex( $this->derive_hover_color( $sidebar_submenu, 8 ), $sidebar_hover );
    $adminbar_hover = $this->normalize_hex( $this->derive_hover_color( $adminbar_submenu_bg, 10 ), $adminbar_submenu_bg );
    $adminbar_submenu_hover = $this->normalize_hex( $this->derive_hover_color( $adminbar_submenu_bg, 8 ), $adminbar_hover );

    $notice_success_bg = $this->mix_hex( $surface, $success, 14 );
    $notice_warning_bg = $this->mix_hex( $surface, $warning, 16 );
    $notice_error_bg = $this->mix_hex( $surface, $error, 14 );
    $notice_info_bg = $this->mix_hex( $surface, $primary, 12 );

    $resolved = array_merge(
      $tokens,
      array(
        'color_primary'             => $primary,
        'color_bg'                  => $bg,
        'color_surface'             => $surface,
        'color_text'                => $text,
        'color_muted'               => $muted,
        'sidebar_bg'                => $sidebar_bg,
        'sidebar_text'              => $sidebar_text,
        'adminbar_bg'               => $adminbar_bg,
        'adminbar_text'             => $adminbar_text,
        'adminbar_submenu_bg'       => $adminbar_submenu_bg,
        'adminbar_submenu_text'     => $adminbar_submenu_text,
        'color_border'              => $border,
        'color_hover'               => $hover,
        'color_focus'               => $focus,
        'color_success'             => $success,
        'color_warning'             => $warning,
        'color_error'               => $error,
        'color_table_head'          => $table_head,
        'color_input_bg'            => $input_bg,
        'border_radius'             => (string) $radius,
        'type_scale'                => (string) $type_scale,
        'spacing_scale'             => (string) $spacing_scale,
        'density'                   => $density,
        'resolved_type_scale'       => $type_scale / 100,
        'resolved_density_scale'    => $density_scale,
        'resolved_space_scale'      => $space_scale,
        'resolved_button_radius'    => $this->resolve_button_radius( $radius, isset( $tokens['button_radius_preset'] ) ? $tokens['button_radius_preset'] : 'inherit' ),
        'resolved_focus_shadow'     => $this->hex_to_rgba( $focus, 0.18 ),
        'resolved_on_primary'       => $this->choose_contrast_color( $primary ),
        'resolved_on_surface'       => $this->resolve_accessible_foreground( $surface, $text, array( '#0f172a', '#f8fafc' ) ),
        'resolved_on_sidebar'       => $this->resolve_accessible_foreground( $sidebar_bg, $sidebar_text, array( '#f8fafc', '#0f172a' ) ),
        'resolved_on_sidebar_hover' => $this->resolve_accessible_foreground( $sidebar_hover, $sidebar_text, array( $sidebar_text, '#f8fafc', '#0f172a' ) ),
        'resolved_on_sidebar_submenu' => $this->resolve_accessible_foreground( $sidebar_submenu, $sidebar_text, array( $sidebar_text, '#0f172a', '#f8fafc' ) ),
        'resolved_on_sidebar_submenu_hover' => $this->resolve_accessible_foreground( $sidebar_submenu_hover, $sidebar_text, array( $sidebar_text, '#0f172a', '#f8fafc' ) ),
        'resolved_on_adminbar'      => $this->resolve_accessible_foreground( $adminbar_bg, $adminbar_text, array( '#f8fafc', '#0f172a' ) ),
        'resolved_on_adminbar_hover' => $this->resolve_accessible_foreground( $adminbar_hover, $adminbar_text, array( $adminbar_text, '#0f172a', '#f8fafc' ) ),
        'resolved_on_adminbar_submenu' => $this->resolve_accessible_foreground( $adminbar_submenu_bg, $adminbar_submenu_text, array( '#f8fafc', '#0f172a' ) ),
        'resolved_on_adminbar_submenu_hover' => $this->resolve_accessible_foreground( $adminbar_submenu_hover, $adminbar_submenu_text, array( $adminbar_submenu_text, '#0f172a', '#f8fafc' ) ),
        'resolved_table_head_text'  => $this->resolve_accessible_foreground( $table_head, $text, array( '#0f172a', '#f8fafc' ) ),
        'resolved_surface_hover'    => $surface_hover,
        'resolved_sidebar_hover'    => $sidebar_hover,
        'resolved_sidebar_submenu'  => $sidebar_submenu,
        'resolved_sidebar_submenu_hover' => $sidebar_submenu_hover,
        'resolved_adminbar_hover'   => $adminbar_hover,
        'resolved_adminbar_submenu_hover' => $adminbar_submenu_hover,
        'resolved_notice_success_bg' => $notice_success_bg,
        'resolved_notice_warning_bg' => $notice_warning_bg,
        'resolved_notice_error_bg'   => $notice_error_bg,
        'resolved_notice_info_bg'    => $notice_info_bg,
        'resolved_notice_success_text' => $this->resolve_accessible_foreground( $notice_success_bg, $text, array( '#0f172a', '#f8fafc' ) ),
        'resolved_notice_warning_text' => $this->resolve_accessible_foreground( $notice_warning_bg, $text, array( '#0f172a', '#f8fafc' ) ),
        'resolved_notice_error_text'   => $this->resolve_accessible_foreground( $notice_error_bg, $text, array( '#0f172a', '#f8fafc' ) ),
        'resolved_notice_info_text'    => $this->resolve_accessible_foreground( $notice_info_bg, $text, array( '#0f172a', '#f8fafc' ) ),
      )
    );

    return $resolved;
  }

  /**
   * Build CSS from token settings.
   *
   * @param array $tokens Tokens.
   * @return string
   */
  public function build_css( $tokens ) {
    $tokens = $this->resolve_tokens( $tokens );
    $smart_contrast = ! empty( $tokens['smart_contrast'] );

    $css = ":root{" .
      "--pg-primary:{$tokens['color_primary']};" .
      "--pg-bg:{$tokens['color_bg']};" .
      "--pg-surface:{$tokens['color_surface']};" .
      "--pg-text:{$tokens['color_text']};" .
      "--pg-muted:{$tokens['color_muted']};" .
      "--pg-sidebar:{$tokens['sidebar_bg']};" .
      "--pg-adminbar:{$tokens['adminbar_bg']};" .
      "--pg-adminbar-submenu:{$tokens['adminbar_submenu_bg']};" .
      "--pg-border:{$tokens['color_border']};" .
      "--pg-hover:{$tokens['color_hover']};" .
      "--pg-focus:{$tokens['color_focus']};" .
      "--pg-success:{$tokens['color_success']};" .
      "--pg-warning:{$tokens['color_warning']};" .
      "--pg-error:{$tokens['color_error']};" .
      "--pg-table-head:{$tokens['color_table_head']};" .
      "--pg-input-bg:{$tokens['color_input_bg']};" .
      "--pg-radius:{$tokens['border_radius']}px;" .
      "--pg-button-radius:{$tokens['resolved_button_radius']};" .
      "--pg-font:{$tokens['font_family']};" .
      "--pg-font-scale:{$tokens['resolved_type_scale']};" .
      "--pg-space-scale:{$tokens['resolved_space_scale']};" .
      "--pg-density-scale:{$tokens['resolved_density_scale']};" .
      "--pg-on-primary:{$tokens['resolved_on_primary']};" .
      "--pg-on-surface:{$tokens['resolved_on_surface']};" .
      "--pg-on-sidebar:{$tokens['resolved_on_sidebar']};" .
      "--pg-on-adminbar:{$tokens['resolved_on_adminbar']};" .
      "--pg-on-adminbar-submenu:{$tokens['resolved_on_adminbar_submenu']};" .
      "--pg-focus-shadow:{$tokens['resolved_focus_shadow']};" .
      "--pg-surface-hover:{$tokens['resolved_surface_hover']};" .
      "--pg-sidebar-hover:{$tokens['resolved_sidebar_hover']};" .
      "--pg-sidebar-submenu:{$tokens['resolved_sidebar_submenu']};" .
      "--pg-sidebar-submenu-hover:{$tokens['resolved_sidebar_submenu_hover']};" .
      "--pg-adminbar-hover:{$tokens['resolved_adminbar_hover']};" .
      "--pg-adminbar-submenu-hover:{$tokens['resolved_adminbar_submenu_hover']};" .
      "--pg-notice-success-bg:{$tokens['resolved_notice_success_bg']};" .
      "--pg-notice-warning-bg:{$tokens['resolved_notice_warning_bg']};" .
      "--pg-notice-error-bg:{$tokens['resolved_notice_error_bg']};" .
      "--pg-notice-info-bg:{$tokens['resolved_notice_info_bg']};" .
      "--pg-notice-success-text:{$tokens['resolved_notice_success_text']};" .
      "--pg-notice-warning-text:{$tokens['resolved_notice_warning_text']};" .
      "--pg-notice-error-text:{$tokens['resolved_notice_error_text']};" .
      "--pg-notice-info-text:{$tokens['resolved_notice_info_text']};" .
      "}" .
      "body.wp-admin{background:var(--pg-bg);color:var(--pg-text);font-family:var(--pg-font);font-size:calc(13px * var(--pg-font-scale));line-height:1.55;}" .
      "body.wp-admin #wpcontent,body.wp-admin #wpbody,body.wp-admin #wpbody-content{color:var(--pg-text);}" .
      "body.wp-admin #adminmenu,body.wp-admin #adminmenuback,body.wp-admin #adminmenuwrap{background:var(--pg-sidebar)!important;color:var(--pg-on-sidebar)!important;}" .
      "body.wp-admin #adminmenu a{color:var(--pg-on-sidebar)!important;}" .
      "body.wp-admin #adminmenu div.wp-menu-image:before,body.wp-admin #adminmenu .awaiting-mod,body.wp-admin #adminmenu .menu-counter,body.wp-admin #adminmenu .update-plugins{color:var(--pg-on-sidebar)!important;}" .
      "body.wp-admin #adminmenu .wp-submenu,body.wp-admin #adminmenu .wp-has-current-submenu .wp-submenu,body.wp-admin #adminmenu .wp-has-current-submenu .wp-submenu.sub-open,body.wp-admin #adminmenu .wp-has-current-submenu.opensub .wp-submenu,body.wp-admin #adminmenu a.wp-has-current-submenu:focus+.wp-submenu,body.wp-admin #adminmenu .opensub .wp-submenu,body.wp-admin #adminmenu .sub-open,body.wp-admin #adminmenu li:hover .wp-submenu{background:var(--pg-sidebar-submenu)!important;border-color:var(--pg-sidebar-submenu)!important;box-shadow:0 10px 30px rgba(15,23,42,.16)!important;}" .
      "body.wp-admin #adminmenu .wp-submenu a,body.wp-admin #adminmenu .wp-has-current-submenu .wp-submenu a,body.wp-admin #adminmenu a.wp-has-current-submenu:focus+.wp-submenu a,body.wp-admin #adminmenu .wp-submenu .wp-submenu-head{color:{$tokens['resolved_on_sidebar_submenu']}!important;}" .
      "body.wp-admin #adminmenu li.menu-top:hover,body.wp-admin #adminmenu li.opensub>a.menu-top,body.wp-admin #adminmenu li>a.menu-top:focus,body.wp-admin #adminmenu li.wp-has-submenu.wp-not-current-submenu:focus-within>a.menu-top{background:var(--pg-sidebar-hover)!important;color:{$tokens['resolved_on_sidebar_hover']}!important;}" .
      "body.wp-admin #adminmenu li.menu-top:hover div.wp-menu-image:before,body.wp-admin #adminmenu li.opensub div.wp-menu-image:before,body.wp-admin #adminmenu li>a.menu-top:focus div.wp-menu-image:before,body.wp-admin #adminmenu li.wp-has-submenu.wp-not-current-submenu:focus-within div.wp-menu-image:before{color:{$tokens['resolved_on_sidebar_hover']}!important;}" .
      "body.wp-admin #adminmenu .current a.menu-top,body.wp-admin #adminmenu .wp-has-current-submenu a.wp-has-current-submenu{background:var(--pg-primary)!important;color:var(--pg-on-primary)!important;}" .
      "body.wp-admin #adminmenu .current div.wp-menu-image:before,body.wp-admin #adminmenu .wp-has-current-submenu div.wp-menu-image:before{color:var(--pg-on-primary)!important;}" .
      "body.wp-admin #adminmenu .wp-has-current-submenu .wp-submenu .wp-submenu-head{background:var(--pg-primary)!important;color:var(--pg-on-primary)!important;border-color:var(--pg-primary)!important;}" .
      "body.wp-admin #adminmenu .wp-submenu a:hover,body.wp-admin #adminmenu .wp-submenu a:focus,body.wp-admin #adminmenu .wp-submenu li.current a,body.wp-admin #adminmenu .opensub .wp-submenu li.current a,body.wp-admin #adminmenu a.wp-has-current-submenu:focus+.wp-submenu li.current a{background:var(--pg-sidebar-submenu-hover)!important;color:{$tokens['resolved_on_sidebar_submenu_hover']}!important;box-shadow:inset 3px 0 0 var(--pg-primary);}" .
      "body.wp-admin #adminmenu li.wp-has-submenu.wp-not-current-submenu.opensub:hover:after,body.wp-admin #adminmenu li.wp-has-submenu.wp-not-current-submenu:focus-within:after{border-right-color:var(--pg-sidebar-submenu)!important;}" .
      "body.wp-admin ul#adminmenu a.wp-has-current-submenu:after,body.wp-admin ul#adminmenu>li.current>a.current:after{border-right-color:var(--pg-surface)!important;}" .
      "body.wp-admin #collapse-button,body.wp-admin #collapse-button .collapse-button-label,body.wp-admin #collapse-button .collapse-button-icon:after{color:var(--pg-on-sidebar)!important;}" .
      "body.wp-admin #collapse-button:hover,body.wp-admin #collapse-button:focus,body.wp-admin #collapse-button:hover .collapse-button-label,body.wp-admin #collapse-button:focus .collapse-button-label,body.wp-admin #collapse-button:hover .collapse-button-icon:after,body.wp-admin #collapse-button:focus .collapse-button-icon:after{color:{$tokens['resolved_on_sidebar_hover']}!important;}" .
      "#wpadminbar{background:var(--pg-adminbar)!important;color:var(--pg-on-adminbar)!important;}" .
      "#wpadminbar .ab-item,#wpadminbar a.ab-item,#wpadminbar>#wp-toolbar span.ab-label,#wpadminbar>#wp-toolbar span.noticon{color:var(--pg-on-adminbar)!important;}" .
      "#wpadminbar .ab-icon:before,#wpadminbar .ab-item:before,#wpadminbar .ab-item:after,#wpadminbar #adminbarsearch:before{color:var(--pg-on-adminbar)!important;}" .
      "#wpadminbar .menupop .ab-sub-wrapper,#wpadminbar .quicklinks .menupop ul.ab-sub-secondary,#wpadminbar .quicklinks .menupop ul.ab-sub-secondary .ab-submenu{background:var(--pg-adminbar-submenu)!important;}" .
      "#wpadminbar .ab-submenu .ab-item,#wpadminbar .ab-submenu a.ab-item,#wpadminbar .ab-submenu .ab-icon:before,#wpadminbar .quicklinks .menupop ul li a,#wpadminbar .quicklinks .menupop.hover ul li a,#wpadminbar.nojs .quicklinks .menupop:hover ul li a,#wpadminbar .menupop .menupop>.ab-item:before,#wpadminbar .quicklinks li .blavatar{color:var(--pg-on-adminbar-submenu)!important;}" .
      "#wpadminbar #wp-admin-bar-user-info .display-name,#wpadminbar #wp-admin-bar-user-info .username{color:var(--pg-on-adminbar-submenu)!important;}" .
      "#wpadminbar .ab-top-menu>li.menupop.hover>.ab-item,#wpadminbar.nojq .quicklinks .ab-top-menu>li>.ab-item:focus,#wpadminbar.nojs .ab-top-menu>li.menupop:hover>.ab-item,#wpadminbar:not(.mobile) .ab-top-menu>li:hover>.ab-item,#wpadminbar:not(.mobile) .ab-top-menu>li>.ab-item:focus{color:{$tokens['resolved_on_adminbar_hover']}!important;background:var(--pg-adminbar-hover)!important;}" .
      "#wpadminbar:not(.mobile)>#wp-toolbar a:focus span.ab-label,#wpadminbar:not(.mobile)>#wp-toolbar li.hover span.ab-label,#wpadminbar:not(.mobile)>#wp-toolbar li:hover span.ab-label,#wpadminbar li .ab-item:focus .ab-icon:before,#wpadminbar li .ab-item:focus:before,#wpadminbar li a:focus .ab-icon:before,#wpadminbar li.hover .ab-icon:before,#wpadminbar li.hover .ab-item:before,#wpadminbar li:hover #adminbarsearch:before,#wpadminbar li:hover .ab-icon:before,#wpadminbar li:hover .ab-item:before{color:{$tokens['resolved_on_adminbar_hover']}!important;}" .
      "#wpadminbar .quicklinks .ab-sub-wrapper .menupop.hover>a,#wpadminbar .quicklinks .menupop ul li a:hover,#wpadminbar .quicklinks .menupop ul li a:focus,#wpadminbar .quicklinks .menupop ul li a:hover strong,#wpadminbar .quicklinks .menupop ul li a:focus strong,#wpadminbar .quicklinks .menupop.hover ul li a:hover,#wpadminbar .quicklinks .menupop.hover ul li a:focus,#wpadminbar.nojs .quicklinks .menupop:hover ul li a:hover,#wpadminbar.nojs .quicklinks .menupop:hover ul li a:focus{background:var(--pg-adminbar-submenu-hover)!important;color:{$tokens['resolved_on_adminbar_submenu_hover']}!important;}" .
      "#wpadminbar #wp-admin-bar-user-info a:hover .display-name,#wpadminbar #wp-admin-bar-user-info a:focus .display-name{color:{$tokens['resolved_on_adminbar_submenu_hover']}!important;}" .
      "body.wp-admin .button.button-primary,body.wp-admin .wp-core-ui .button-primary{background:var(--pg-primary);color:var(--pg-on-primary);border-color:var(--pg-primary);border-radius:var(--pg-button-radius);padding:calc(6px * var(--pg-space-scale)) calc(14px * var(--pg-space-scale));}" .
      "body.wp-admin .button.button-primary:hover,body.wp-admin .button.button-primary:focus,body.wp-admin .wp-core-ui .button-primary:hover,body.wp-admin .wp-core-ui .button-primary:focus{background:var(--pg-primary);color:var(--pg-on-primary);filter:brightness(0.95);}" .
      "body.wp-admin .wp-core-ui .button,body.wp-admin .wp-core-ui .button-secondary,body.wp-admin .page-title-action{border-radius:var(--pg-button-radius);border-color:var(--pg-border);background:var(--pg-surface);color:var(--pg-text);padding:calc(6px * var(--pg-space-scale)) calc(14px * var(--pg-space-scale));}" .
      "body.wp-admin .wp-core-ui .button:hover,body.wp-admin .wp-core-ui .button:focus,body.wp-admin .wp-core-ui .button-secondary:hover,body.wp-admin .wp-core-ui .button-secondary:focus,body.wp-admin .page-title-action:hover,body.wp-admin .page-title-action:focus{background:var(--pg-hover);color:var(--pg-text);border-color:var(--pg-border);}" .
      "body.wp-admin input[type='text'],body.wp-admin input[type='search'],body.wp-admin input[type='email'],body.wp-admin input[type='url'],body.wp-admin input[type='password'],body.wp-admin input[type='number'],body.wp-admin select,body.wp-admin textarea{background:var(--pg-input-bg);color:var(--pg-text);border-color:var(--pg-border);border-radius:var(--pg-radius);}" .
      "body.wp-admin input[type='text']:focus,body.wp-admin input[type='search']:focus,body.wp-admin input[type='email']:focus,body.wp-admin input[type='url']:focus,body.wp-admin input[type='password']:focus,body.wp-admin input[type='number']:focus,body.wp-admin select:focus,body.wp-admin textarea:focus,body.wp-admin .wp-core-ui .button:focus,body.wp-admin a:focus{border-color:var(--pg-focus);box-shadow:0 0 0 1px var(--pg-focus),0 0 0 4px var(--pg-focus-shadow);outline:none;}" .
      "body.wp-admin .wrap h1,body.wp-admin .wrap h2,body.wp-admin .wrap h3,body.wp-admin .wrap h4,body.wp-admin .wrap h5,body.wp-admin .wrap h6,body.wp-admin .wrap .wp-heading-inline,body.wp-admin .postbox .inside h2,body.wp-admin .stuffbox .inside h2{color:var(--pg-text)!important;}" .
      "body.wp-admin .wp-list-table tbody tr,body.wp-admin .widefat tbody tr{background:var(--pg-surface);color:var(--pg-text);}" .
      "body.wp-admin .striped>tbody>:nth-child(odd),body.wp-admin ul.striped>:nth-child(odd),body.wp-admin .alternate{background:var(--pg-surface-hover)!important;color:var(--pg-text)!important;}" .
      "body.wp-admin .wp-list-table tbody td,body.wp-admin .wp-list-table tbody th,body.wp-admin .widefat tbody td,body.wp-admin .widefat tbody th{color:var(--pg-text)!important;border-color:var(--pg-border)!important;}" .
      "body.wp-admin .postbox-header,body.wp-admin .postbox .hndle,body.wp-admin .stuffbox .hndle,body.wp-admin .widget-top,body.wp-admin .widget .widget-top,body.wp-admin .sidebar-name{background:var(--pg-table-head)!important;color:{$tokens['resolved_table_head_text']}!important;border-color:var(--pg-border)!important;}" .
      "body.wp-admin .widget-title h3,body.wp-admin .widget-title h4,body.wp-admin .sidebar-name h2,body.wp-admin .sidebar-name h3{color:{$tokens['resolved_table_head_text']}!important;}" .
      "body.wp-admin .postbox .inside,body.wp-admin .stuffbox .inside,body.wp-admin .widget-inside,body.wp-admin .widgets-holder-wrap,body.wp-admin .widgets-sortables,body.wp-admin .accordion-section-content,body.wp-admin .categorydiv div.tabs-panel,body.wp-admin .customlinkdiv div.tabs-panel,body.wp-admin .tabs-panel,body.wp-admin #post-status-info{background:var(--pg-surface)!important;color:var(--pg-text)!important;border-color:var(--pg-border)!important;}" .
      "body.wp-admin .widget-control-edit,body.wp-admin .widgets-chooser ul,body.wp-admin .widgets-chooser li,body.wp-admin #widgets-left .inactive-sidebar{background:var(--pg-surface)!important;color:var(--pg-text)!important;border-color:var(--pg-border)!important;}" .
      "body.wp-admin .widgets-holder-wrap .description,body.wp-admin .sidebar-description,body.wp-admin .widget-description,body.wp-admin .in-widget-title,body.wp-admin #available-widgets .widget-description,body.wp-admin #widgets-right a.widget-control-edit{color:var(--pg-muted)!important;}" .
      "body.wp-admin .handlediv,body.wp-admin .item-edit,body.wp-admin .postbox .handlediv.button-link,body.wp-admin .toggle-indicator,body.wp-admin .widget-action,body.wp-admin .sidebar-name .toggle-indicator:before,body.wp-admin .postbox .handle-order-higher .order-higher-indicator::before,body.wp-admin .postbox .handle-order-lower .order-lower-indicator::before{color:{$tokens['resolved_table_head_text']}!important;}" .
      "body.wp-admin .handlediv:hover,body.wp-admin .handlediv:focus,body.wp-admin .item-edit:hover,body.wp-admin .item-edit:focus,body.wp-admin .widget-top:hover .widget-action,body.wp-admin .sidebar-name:hover .toggle-indicator{color:var(--pg-text)!important;}" .
      "body.wp-admin .submitbox .inside,body.wp-admin #minor-publishing-actions,body.wp-admin #major-publishing-actions,body.wp-admin .misc-pub-section,body.wp-admin #misc-publishing-actions{background:var(--pg-surface)!important;color:var(--pg-text)!important;border-color:var(--pg-border)!important;}" .
      "body.wp-admin #major-publishing-actions{background:var(--pg-surface-hover)!important;}" .
      "body.wp-admin .submitbox .submitdelete,body.wp-admin .submitbox .submit a,body.wp-admin .misc-pub-section a,body.wp-admin #post-status-info a{color:var(--pg-primary)!important;}" .
      "body.wp-admin #titlediv #title,body.wp-admin #titlewrap .skiplink,body.wp-admin .wp-editor-wrap .wp-editor-tools,body.wp-admin .wp-editor-container,body.wp-admin .quicktags-toolbar,body.wp-admin .mce-toolbar-grp,body.wp-admin .mce-statusbar,body.wp-admin .mce-top-part::before,body.wp-admin button.wp-switch-editor{background:var(--pg-surface)!important;color:var(--pg-text)!important;border-color:var(--pg-border)!important;}" .
      "body.wp-admin #titlediv #title-prompt-text,body.wp-admin #comment-link-box,body.wp-admin #edit-slug-box{color:var(--pg-muted)!important;}" .
      "body.wp-admin .mce-toolbar .mce-btn button,body.wp-admin .mce-menubar .mce-menubtn button,body.wp-admin .quicktags-toolbar input{color:var(--pg-text)!important;}" .
      "body.wp-admin .mce-ico,body.wp-admin .mce-caret{color:var(--pg-text)!important;border-top-color:var(--pg-text)!important;}" .
      "body.wp-admin ul.category-tabs li.tabs,body.wp-admin ul.add-menu-item-tabs li.tabs,body.wp-admin .wp-tab-bar .wp-tab-active{background:var(--pg-table-head)!important;border-color:var(--pg-border)!important;}" .
      "body.wp-admin .category-tabs a,body.wp-admin .add-menu-item-tabs a,body.wp-admin .wp-tab-bar a{color:var(--pg-muted)!important;}" .
      "body.wp-admin ul.category-tabs li.tabs a,body.wp-admin ul.add-menu-item-tabs li.tabs a,body.wp-admin .wp-tab-bar .wp-tab-active a{color:{$tokens['resolved_table_head_text']}!important;}" .
      "body.wp-admin .wrap,body.wp-admin .postbox,body.wp-admin .card,body.wp-admin .form-wrap,body.wp-admin .stuffbox{color:var(--pg-text);}" .
      "body.wp-admin .postbox,body.wp-admin .card,body.wp-admin .stuffbox,body.wp-admin .widefat,body.wp-admin .wp-list-table{background:var(--pg-surface);border-color:var(--pg-border);color:var(--pg-text);}" .
      "body.wp-admin .wp-list-table tbody tr:hover,body.wp-admin .widefat tbody tr:hover{background:var(--pg-surface-hover);}" .
      "body.wp-admin .widefat thead td,body.wp-admin .widefat thead th,body.wp-admin .wp-list-table thead td,body.wp-admin .wp-list-table thead th,body.wp-admin .wp-list-table tfoot td,body.wp-admin .wp-list-table tfoot th{background:var(--pg-table-head);color:{$tokens['resolved_table_head_text']};border-color:var(--pg-border);}" .
      "body.wp-admin .notice{border-color:var(--pg-border);border-radius:var(--pg-radius);}" .
      "body.wp-admin .notice-success,body.wp-admin div.updated{background:var(--pg-notice-success-bg);color:var(--pg-notice-success-text);border-left-color:var(--pg-success);}" .
      "body.wp-admin .notice-warning{background:var(--pg-notice-warning-bg);color:var(--pg-notice-warning-text);border-left-color:var(--pg-warning);}" .
      "body.wp-admin .notice-error{background:var(--pg-notice-error-bg);color:var(--pg-notice-error-text);border-left-color:var(--pg-error);}" .
      "body.wp-admin .notice-info,body.wp-admin .update-nag{background:var(--pg-notice-info-bg);color:var(--pg-notice-info-text);border-left-color:var(--pg-primary);}" .
      "body.wp-admin #pdt-wrap,body.wp-admin .pdt-design-form .pdt-settings-section{background:var(--pg-surface);color:var(--pg-on-surface);border-color:var(--pg-border);}" .
      "body.wp-admin #pdt-wrap .description,body.wp-admin #pdt-wrap .pdt-toggle-label,body.wp-admin #pdt-wrap .pdt-help-tooltip__toggle{color:var(--pg-muted);}" .
      "body.wp-admin #pdt-wrap .pdt-header,body.wp-admin #pdt-wrap .pdt-header .pdt-title,body.wp-admin #pdt-wrap .pdt-header .pdt-subtitle,body.wp-admin #pdt-wrap .pdt-header p{color:#fff!important;}" .
      "body.wp-admin #pdt-wrap .pdt-header .pdt-subtitle{opacity:.92;}" .
      "body.wp-admin #pdt-wrap h3,body.wp-admin #pdt-wrap label,body.wp-admin #pdt-wrap .pdt-design-field-label label{color:var(--pg-on-surface);}" .
      "body.wp-admin #pdt-wrap select,body.wp-admin #pdt-wrap input[type='number'],body.wp-admin #pdt-wrap input[type='color'],body.wp-admin #pdt-wrap textarea{background:var(--pg-input-bg);color:var(--pg-on-surface);border-color:var(--pg-border);}" .
      "body.wp-admin #pdt-wrap .pdt-help-tooltip__toggle{border-color:var(--pg-border);background:var(--pg-surface);}" .
      "body.wp-admin #wpbody-content a{color:var(--pg-primary);}" .
      "body.wp-admin #wpbody-content a:hover,body.wp-admin #wpbody-content a:focus{color:var(--pg-primary);filter:brightness(0.92);}";

    if ( ! empty( $tokens['link_enabled'] ) ) {
      $css .= $this->build_link_css( $tokens );
    }

    if ( isset( $tokens['preset'] ) && 'dark' === $tokens['preset'] ) {
      $css .=
        "body.wp-admin select,body.wp-admin .wp-core-ui select{color-scheme:dark;}" .
        "body.wp-admin select:hover,body.wp-admin select:focus,body.wp-admin .wp-core-ui select:hover,body.wp-admin .wp-core-ui select:focus{background:var(--pg-input-bg)!important;color:var(--pg-text)!important;border-color:var(--pg-focus)!important;}" .
        "body.wp-admin select option,body.wp-admin select optgroup,body.wp-admin .wp-core-ui select option,body.wp-admin .wp-core-ui select optgroup{background:var(--pg-surface)!important;color:var(--pg-text)!important;}" .
        "body.wp-admin select option:checked,body.wp-admin .wp-core-ui select option:checked{background:var(--pg-table-head)!important;color:{$tokens['resolved_table_head_text']}!important;}" .
        "body.wp-admin select:disabled option,body.wp-admin .wp-core-ui select:disabled option{color:var(--pg-muted)!important;}";
    }

    if ( $smart_contrast ) {
      $css .=
        "body.wp-admin .postbox,body.wp-admin .postbox-header,body.wp-admin .card,body.wp-admin .stuffbox,body.wp-admin .widefat,body.wp-admin .wp-list-table,body.wp-admin .form-table th,body.wp-admin .form-table td{background:var(--pg-surface)!important;color:var(--pg-on-surface)!important;border-color:var(--pg-border)!important;}" .
        "body.wp-admin .wp-list-table thead td,body.wp-admin .wp-list-table thead th,body.wp-admin .widefat thead td,body.wp-admin .widefat thead th,body.wp-admin .wp-list-table tfoot td,body.wp-admin .wp-list-table tfoot th{background:var(--pg-table-head)!important;color:{$tokens['resolved_table_head_text']}!important;}" .
        "body.wp-admin .wp-core-ui .button-secondary{background:var(--pg-surface)!important;color:var(--pg-on-surface)!important;border-color:var(--pg-border)!important;}" .
        "body.wp-admin .wp-core-ui .button-secondary:hover,body.wp-admin .wp-core-ui .button-secondary:focus{background:var(--pg-hover)!important;color:var(--pg-on-surface)!important;border-color:var(--pg-border)!important;}";
    }

    // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Public addon extension hook.
    $css = apply_filters( 'pdt_design_generated_css', $css, $tokens, $this->settings );

    if ( ! empty( $tokens['custom_css'] ) ) {
      $css .= "\n/* Custom CSS */\n" . trim( $tokens['custom_css'] );
    }

    return $css;
  }

  /**
   * Build the optional "Links" customization CSS block.
   *
   * @param array $tokens Resolved settings tokens.
   * @return string
   */
  private function build_link_css( $tokens ) {
    $color   = isset( $tokens['link_color'] ) ? $tokens['link_color'] : '#4f46e5';
    $hover   = isset( $tokens['link_hover'] ) ? $tokens['link_hover'] : $color;
    $visited = isset( $tokens['link_visited'] ) ? $tokens['link_visited'] : $color;
    $active  = isset( $tokens['link_active'] ) ? $tokens['link_active'] : $hover;
    $weight  = isset( $tokens['link_weight'] ) ? $tokens['link_weight'] : 'inherit';
    $deco    = isset( $tokens['link_decoration'] ) ? $tokens['link_decoration'] : 'hover';
    $scope   = isset( $tokens['link_scope'] ) ? $tokens['link_scope'] : 'content_only';

    $exclude = ':not(.button):not(.button-primary):not(.button-secondary):not(.page-title-action):not(.add-new-h2)';

    if ( 'all_admin' === $scope ) {
      $bases = array( 'body.wp-admin #wpbody-content a' . $exclude );
    } else {
      $roots = array(
        '.wrap',
        '.postbox',
        '.card',
        '.stuffbox',
        '.form-table',
        '.description',
        '.notice',
        '#pdt-wrap',
      );
      $bases = array();
      foreach ( $roots as $root ) {
        $bases[] = 'body.wp-admin ' . $root . ' a' . $exclude;
      }
    }

    $base_selector    = implode( ',', $bases );
    $hover_selector   = implode( ',', array_map( static function ( $sel ) { return $sel . ':hover'; }, $bases ) );
    $focus_selector   = implode( ',', array_map( static function ( $sel ) { return $sel . ':focus'; }, $bases ) );
    $active_selector  = implode( ',', array_map( static function ( $sel ) { return $sel . ':active'; }, $bases ) );
    $visited_selector = implode( ',', array_map( static function ( $sel ) { return $sel . ':visited'; }, $bases ) );

    if ( 'none' === $deco ) {
      $base_deco  = 'text-decoration:none;';
      $hover_deco = 'text-decoration:none;';
    } elseif ( 'underline' === $deco ) {
      $base_deco  = 'text-decoration:underline;';
      $hover_deco = 'text-decoration:underline;';
    } else {
      $base_deco  = 'text-decoration:none;';
      $hover_deco = 'text-decoration:underline;';
    }

    $weight_decl = ( 'inherit' === $weight ) ? '' : 'font-weight:' . absint( $weight ) . ';';

    $css  = ':root{--pg-link:' . $color . ';--pg-link-hover:' . $hover . ';--pg-link-visited:' . $visited . ';--pg-link-active:' . $active . ';}';
    $css .= $base_selector . '{color:var(--pg-link);' . $base_deco . $weight_decl . '}';
    $css .= $visited_selector . '{color:var(--pg-link-visited);}';
    $css .= $hover_selector . '{color:var(--pg-link-hover);' . $hover_deco . '}';
    $css .= $focus_selector . ',' . $active_selector . '{color:var(--pg-link-active);' . $hover_deco . '}';

    return $css;
  }

  /**
   * Compile and persist generated CSS when token hash changes.
   *
   * @param array $settings Design settings.
   * @return void
   */
  public function compile_if_needed( $settings ) {
    $css = $this->build_css( $settings );
    $hash = md5( $css );
    $meta = $this->settings->get_css_meta();

    if ( isset( $meta['hash'] ) && $meta['hash'] === $hash && ! empty( $meta['path'] ) && file_exists( $meta['path'] ) ) {
      return;
    }

    $upload_dir = wp_upload_dir();
    if ( ! empty( $upload_dir['error'] ) ) {
      return;
    }

    $target_dir = trailingslashit( $upload_dir['basedir'] ) . 'polanger-design';
    $target_url = trailingslashit( $upload_dir['baseurl'] ) . 'polanger-design';

    if ( ! wp_mkdir_p( $target_dir ) ) {
      return;
    }

    $filename = 'design-' . $hash . '.css';
    $path = trailingslashit( $target_dir ) . $filename;
    $bytes = file_put_contents( $path, $css, LOCK_EX );

    if ( false === $bytes ) {
      return;
    }

    if ( ! empty( $meta['path'] ) && $meta['path'] !== $path && file_exists( $meta['path'] ) ) {
      @unlink( $meta['path'] );
    }

    $this->settings->set_css_meta(
      array(
        'hash' => $hash,
        'path' => $path,
        'url'  => trailingslashit( $target_url ) . $filename,
      )
    );
  }

  /**
   * Resolve a numeric select setting.
   *
   * @param string|int $value Selected value.
   * @param array      $allowed Allowed integer values.
   * @param int        $fallback Fallback value.
   * @return int
   */
  private function resolve_numeric_option( $value, $allowed, $fallback ) {
    $value = absint( $value );
    return in_array( $value, $allowed, true ) ? $value : absint( $fallback );
  }

  /**
   * Normalize hex values.
   *
   * @param string $hex Input color.
   * @param string $fallback Fallback hex color.
   * @return string
   */
  private function normalize_hex( $hex, $fallback ) {
    $hex = sanitize_hex_color( $hex );
    if ( $hex ) {
      return $hex;
    }

    $fallback = sanitize_hex_color( $fallback );
    return $fallback ? $fallback : '#0f172a';
  }

  /**
   * Choose the best contrast color using WCAG contrast ratio.
   *
   * @param string $background Background hex color.
   * @param array  $candidates Candidate foreground colors.
   * @return string
   */
  private function choose_contrast_color( $background, $candidates = array() ) {
    $background = $this->normalize_hex( $background, '#ffffff' );
    $candidates = is_array( $candidates ) ? $candidates : array();
    $candidates = array_filter(
      array_unique(
        array_merge(
          $candidates,
          array( '#0f172a', '#f8fafc' )
        )
      )
    );

    $best_color = '#0f172a';
    $best_ratio = 0;

    foreach ( $candidates as $candidate ) {
      $candidate = $this->normalize_hex( $candidate, '' );
      if ( empty( $candidate ) ) {
        continue;
      }

      $ratio = $this->contrast_ratio( $background, $candidate );
      if ( $ratio > $best_ratio ) {
        $best_ratio = $ratio;
        $best_color = $candidate;
      }
    }

    return $best_color;
  }

  /**
   * Keep a preferred foreground when it already meets WCAG contrast.
   *
   * @param string $background Background color.
   * @param string $preferred Preferred foreground color.
   * @param array  $fallback_candidates Fallback candidates.
   * @param float  $minimum_ratio Minimum acceptable ratio.
   * @return string
   */
  private function resolve_accessible_foreground( $background, $preferred, $fallback_candidates = array(), $minimum_ratio = 4.5 ) {
    $preferred = sanitize_hex_color( $preferred );
    if ( $preferred && $this->contrast_ratio( $background, $preferred ) >= $minimum_ratio ) {
      return $preferred;
    }

    return $this->choose_contrast_color( $background, $fallback_candidates );
  }

  /**
   * Calculate contrast ratio between two colors.
   *
   * @param string $left Left color.
   * @param string $right Right color.
   * @return float
   */
  private function contrast_ratio( $left, $right ) {
    $left_luminance = $this->relative_luminance( $left );
    $right_luminance = $this->relative_luminance( $right );

    $lighter = max( $left_luminance, $right_luminance );
    $darker = min( $left_luminance, $right_luminance );

    return ( $lighter + 0.05 ) / ( $darker + 0.05 );
  }

  /**
   * Relative luminance for a hex color.
   *
   * @param string $hex Hex color.
   * @return float
   */
  private function relative_luminance( $hex ) {
    $rgb = $this->hex_to_rgb( $hex );
    if ( empty( $rgb ) ) {
      return 0;
    }

    $channels = array();
    foreach ( array( 'r', 'g', 'b' ) as $channel ) {
      $value = $rgb[ $channel ] / 255;
      $channels[ $channel ] = ( $value <= 0.03928 ) ? ( $value / 12.92 ) : pow( ( ( $value + 0.055 ) / 1.055 ), 2.4 );
    }

    return ( 0.2126 * $channels['r'] ) + ( 0.7152 * $channels['g'] ) + ( 0.0722 * $channels['b'] );
  }

  /**
   * Mix two hex colors by percentage.
   *
   * @param string $base Base hex color.
   * @param string $mix Mix hex color.
   * @param int    $percent Percent of mix color.
   * @return string
   */
  private function mix_hex( $base, $mix, $percent ) {
    $base_rgb = $this->hex_to_rgb( $base );
    $mix_rgb = $this->hex_to_rgb( $mix );
    $ratio = max( 0, min( 100, absint( $percent ) ) ) / 100;

    if ( empty( $base_rgb ) || empty( $mix_rgb ) ) {
      return $this->normalize_hex( $base, '#ffffff' );
    }

    $mixed = array();
    foreach ( array( 'r', 'g', 'b' ) as $channel ) {
      $mixed[ $channel ] = (int) round( $base_rgb[ $channel ] + ( ( $mix_rgb[ $channel ] - $base_rgb[ $channel ] ) * $ratio ) );
    }

    return sprintf( '#%02x%02x%02x', $mixed['r'], $mixed['g'], $mixed['b'] );
  }

  /**
   * Derive a hover shade depending on the base brightness.
   *
   * @param string $base Base color.
   * @param int    $percent Mix percentage.
   * @return string
   */
  private function derive_hover_color( $base, $percent ) {
    $base = $this->normalize_hex( $base, '#ffffff' );
    $target = $this->relative_luminance( $base ) > 0.55 ? '#0f172a' : '#ffffff';
    return $this->mix_hex( $base, $target, $percent );
  }

  /**
   * Convert hex color to rgb values.
   *
   * @param string $hex Hex color.
   * @return array<string,int>
   */
  private function hex_to_rgb( $hex ) {
    $hex = ltrim( (string) $this->normalize_hex( $hex, '' ), '#' );
    if ( 6 !== strlen( $hex ) ) {
      return array();
    }

    return array(
      'r' => hexdec( substr( $hex, 0, 2 ) ),
      'g' => hexdec( substr( $hex, 2, 2 ) ),
      'b' => hexdec( substr( $hex, 4, 2 ) ),
    );
  }

  /**
   * Convert hex color to rgba string.
   *
   * @param string $hex Hex color.
   * @param float  $alpha Alpha value.
   * @return string
   */
  private function hex_to_rgba( $hex, $alpha ) {
    $rgb = $this->hex_to_rgb( $hex );
    if ( empty( $rgb ) ) {
      return 'rgba(15,23,42,0.16)';
    }

    $alpha = max( 0, min( 1, (float) $alpha ) );

    return sprintf( 'rgba(%d,%d,%d,%.3f)', $rgb['r'], $rgb['g'], $rgb['b'], $alpha );
  }

  /**
   * Resolve button radius from global radius and preset.
   *
   * @param int    $radius Base radius.
   * @param string $preset Preset key.
   * @return string
   */
  private function resolve_button_radius( $radius, $preset ) {
    switch ( $preset ) {
      case 'soft':
        return max( 4, $radius ) . 'px';
      case 'rounded':
        return min( 20, max( 8, $radius + 6 ) ) . 'px';
      case 'pill':
        return '999px';
      case 'inherit':
      default:
        return $radius . 'px';
    }
  }
}
