<?php

namespace Calio\Widgets;

use Elementor\Controls_Manager;
use Elementor\Icons_Manager;
use Elementor\Widget_Base;

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

/**
 * Provides shared Elementor category and keyword defaults for all Calio widgets.
 */
abstract class BaseWidget extends Widget_Base
{

    /**
     * Adds a lightweight heading so long style sections read more like grouped control panels.
     *
     * @param string $id    Unique control id.
     * @param string $label Heading label shown in the editor.
     * @param array  $args  Optional condition arguments.
     */
    protected function add_style_heading(string $id, string $label, array $args = []): void
    {
        $this->add_control(
            $id,
            [
                'label' => $label,
                'type' => Controls_Manager::HEADING,
                'separator' => $args['separator'] ?? 'default',
                'condition' => $args['condition'] ?? [],
            ]
        );
    }

    /**
     * Registers a four-side dimensions control for spacing or radius values.
     *
     * @param string          $id        Unique control id.
     * @param string          $label     Visible label.
     * @param string|string[] $selectors Target selector or selector list.
     * @param string          $property  CSS property to write.
     * @param array           $args      Optional condition/default arguments.
     */
    protected function add_dimensions_control(string $id, string $label, $selectors, string $property, array $args = []): void
    {
        $selectors = is_array($selectors) ? $selectors : [$selectors];
        $selector_map = [];

        foreach ($selectors as $selector) {
            $selector_map[(string) $selector] = sprintf(
                '%s: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
                $property
            );
        }

        $this->add_control(
            $id,
            [
                'label' => $label,
                'type' => Controls_Manager::DIMENSIONS,
                'size_units' => $args['size_units'] ?? ['px', '%', 'em'],
                'selectors' => $selector_map,
                'condition' => $args['condition'] ?? [],
            ]
        );
    }

    /**
     * Keeps all widgets grouped under the Calio Elementor category.
     */
    public function get_categories(): array
    {
        return ['calio-widgets'];
    }

    /**
     * Adds consistent search keywords so widgets are easier to find in the editor.
     */
    public function get_keywords(): array
    {
        return ['calio', 'addon', 'elementor'];
    }

    /**
     * Forces Elementor editor to reload the widget markup instead of trying 
     * to inject CSS/JS dynamically for stateful/complex components.
     */
    public function is_reload_preview_required()
    {
        return true;
    }

    /**
     * Renders Elementor icon controls as sanitized HTML instead of printing raw
     * output directly from Icons_Manager.
     *
     * @param mixed                $icon       Elementor icon control payload.
     * @param array<string,string> $attributes Extra icon attributes.
     */
    protected function get_icon_markup($icon, array $attributes = []): string
    {
        if (!is_array($icon) || empty($icon['value'])) {
            return '';
        }

        ob_start();
        Icons_Manager::render_icon($icon, array_merge(['aria-hidden' => 'true'], $attributes));
        $markup = trim((string) ob_get_clean());

        return $markup === '' ? '' : (string) wp_kses($markup, $this->calio_allowed_icon_markup());
    }

    /**
     * @return array<string, array<string, bool>>
     */
    protected function calio_allowed_icon_markup(): array
    {
        $global_attributes = [
            'aria-hidden' => true,
            'class' => true,
            'focusable' => true,
            'height' => true,
            'role' => true,
            'viewbox' => true,
            'width' => true,
            'xmlns' => true,
        ];

        return [
            'i' => $global_attributes,
            'span' => $global_attributes,
            'svg' => $global_attributes,
            'path' => [
                'clip-rule' => true,
                'd' => true,
                'fill' => true,
                'fill-rule' => true,
                'stroke' => true,
                'stroke-linecap' => true,
                'stroke-linejoin' => true,
                'stroke-width' => true,
            ],
            'g' => $global_attributes,
            'use' => [
                'href' => true,
                'xlink:href' => true,
            ],
        ];
    }

    /**
     * Standardized widget styles contract:
     * editor/preview always use global Calio styles, frontend uses widget handle.
     */
    public function get_style_depends(): array
    {
        return $this->resolve_widget_style_depends();
    }

    /**
     * Standardized widget scripts contract:
     * editor/preview always use global Calio scripts, frontend uses widget handle.
     */
    public function get_script_depends(): array
    {
        return $this->resolve_widget_script_depends();
    }

    protected function resolve_widget_style_depends(?string $frontendHandle = null): array
    {
        if ($this->is_calio_edit_or_preview_mode()) {
            return ['calio-styles'];
        }

        $handle = $frontendHandle ?: $this->infer_widget_asset_handle();

        return $handle !== '' ? [$handle] : [];
    }

    protected function resolve_widget_script_depends(?string $frontendHandle = null): array
    {
        if ($this->is_calio_edit_or_preview_mode()) {
            return ['calio-scripts'];
        }

        $handle = $frontendHandle ?: $this->infer_widget_asset_handle();

        return $handle !== '' ? [$handle] : [];
    }

    /**
     * Infers default frontend widget asset handle from Elementor widget name.
     */
    protected function infer_widget_asset_handle(): string
    {
        $name = (string) $this->get_name();
        if ($name === '') {
            return '';
        }

        $slug = preg_replace('/^calio-/', '', $name);
        if (!is_string($slug) || $slug === '') {
            return '';
        }

        return 'calio-widget-' . $slug;
    }

    protected function is_calio_edit_or_preview_mode(): bool
    {
        if (!class_exists('\Elementor\Plugin')) {
            return false;
        }

        $plugin = \Elementor\Plugin::$instance ?? null;
        if (!is_object($plugin)) {
            return false;
        }

        $isPreview = isset($plugin->preview) && method_exists($plugin->preview, 'is_preview_mode') && $plugin->preview->is_preview_mode();
        $isEditor = isset($plugin->editor) && method_exists($plugin->editor, 'is_edit_mode') && $plugin->editor->is_edit_mode();

        return $isPreview || $isEditor;
    }


    /**
     * Registers a classic/gradient text paint control group for a selector.
     *
     * @param string          $prefix    Unique control prefix.
     * @param string          $label     Visible control label.
     * @param string|string[] $selectors Target selector or selector list.
     * @param array           $args      Optional condition/default arguments.
     */
    protected function add_text_paint_controls(string $prefix, string $label, $selectors, array $args = []): void
    {
        $this->add_paint_controls($prefix, $label, $selectors, 'text', $args);
    }

    /**
     * Registers a classic/gradient background paint control group for a selector.
     *
     * @param string          $prefix    Unique control prefix.
     * @param string          $label     Visible control label.
     * @param string|string[] $selectors Target selector or selector list.
     * @param array           $args      Optional condition/default arguments.
     */
    protected function add_background_paint_controls(string $prefix, string $label, $selectors, array $args = []): void
    {
        $this->add_paint_controls($prefix, $label, $selectors, 'background', $args);
    }

    /**
     * Registers Elementor's native Background Type group control for one or more selectors.
     *
     * This gives the editor the familiar Classic / Gradient background UI instead of
     * only exposing isolated color fields.
     *
     * @param string          $name      Unique group control name.
     * @param string|string[] $selectors Target selector or selector list.
     * @param array           $args      Optional condition/type arguments.
     */
    protected function add_background_type_control(string $name, $selectors, array $args = []): void
    {
        $selectors = is_array($selectors) ? $selectors : [$selectors];
        $selector_string = implode(', ', array_map('strval', $selectors));
        $control_args = [
            'name' => $name,
            'types' => $args['types'] ?? ['classic', 'gradient'],
            'selector' => $selector_string,
        ];

        if (!empty($args['condition'])) {
            $control_args['condition'] = $args['condition'];
        }

        if (!empty($args['exclude'])) {
            // Elementor control option, not a WP_Query exclusion parameter.
            // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude
            $control_args['exclude'] = $args['exclude'];
        }

        if (!empty($args['fields_options'])) {
            $control_args['fields_options'] = $args['fields_options'];
        }

        $this->add_group_control(\Elementor\Group_Control_Background::get_type(), $control_args);
    }

    /**
     * Registers a classic/gradient border paint control group for a selector.
     *
     * @param string          $prefix    Unique control prefix.
     * @param string          $label     Visible control label.
     * @param string|string[] $selectors Target selector or selector list.
     * @param array           $args      Optional condition/default arguments.
     */
    protected function add_border_paint_controls(string $prefix, string $label, $selectors, array $args = []): void
    {
        $this->add_paint_controls($prefix, $label, $selectors, 'border', $args);
    }

    /**
     * Registers a classic/gradient indicator control for underline/card tab states.
     *
     * @param string          $prefix             Unique control prefix.
     * @param string          $label              Visible control label.
     * @param string|string[] $underlineSelectors Selectors that render an underline-style indicator.
     * @param string|string[] $borderSelectors    Selectors that render a border-style indicator.
     * @param array           $args               Optional condition/default arguments.
     */
    protected function add_indicator_paint_controls(
        string $prefix,
        string $label,
        $underlineSelectors,
        $borderSelectors = [],
        array $args = []
    ): void {
        $condition = $args['condition'] ?? [];
        $default_mode = $args['default_mode'] ?? 'classic';
        $wrapper_class = 'calio-' . str_replace('_', '-', sanitize_key($prefix)) . '-paint-';
        $var_prefix = '--' . str_replace('_', '-', sanitize_key($prefix)) . '-paint';

        $this->add_control(
            $prefix . '_paint_mode',
            [
                'label' => $label,
                'type' => Controls_Manager::CHOOSE,
                'options' => [
                    'classic' => [
                        'title' => esc_html__('Classic', 'calio-addon'),
                        'icon' => 'eicon-paint-brush',
                    ],
                    'gradient' => [
                        'title' => esc_html__('Gradient', 'calio-addon'),
                        'icon' => 'eicon-barcode',
                    ],
                ],
                'default' => $default_mode,
                'toggle' => false,
                'prefix_class' => $wrapper_class,
                'condition' => $condition,
            ]
        );

        foreach (
            [
                'color' => esc_html__('Classic Color', 'calio-addon'),
                'gradient_start' => esc_html__('Gradient Start', 'calio-addon'),
                'gradient_end' => esc_html__('Gradient End', 'calio-addon'),
            ] as $variant => $variant_label
        ) {
            $control_id = $prefix . '_' . $variant;
            $is_classic = $variant === 'color';
            $variant_key = $is_classic ? 'classic' : $variant;
            $control_args = [
                'label' => $variant_label,
                'type' => Controls_Manager::COLOR,
                'condition' => array_merge($condition, [$prefix . '_paint_mode' => $is_classic ? 'classic' : 'gradient']),
                'selectors' => $this->build_indicator_selectors(
                    is_array($underlineSelectors) ? $underlineSelectors : [$underlineSelectors],
                    is_array($borderSelectors) ? $borderSelectors : [$borderSelectors],
                    $wrapper_class . ($is_classic ? 'classic' : 'gradient'),
                    $variant_key,
                    $var_prefix
                ),
            ];
            $this->add_control($control_id, $control_args);
        }

        $this->add_control(
            $prefix . '_gradient_angle',
            [
                'label' => esc_html__('Gradient Angle', 'calio-addon'),
                'type' => Controls_Manager::SLIDER,
                'size_units' => ['deg'],
                'range' => [
                    'deg' => [
                        'min' => 0,
                        'max' => 360,
                    ],
                ],
                'default' => [
                    'unit' => 'deg',
                    'size' => 135,
                ],
                'condition' => array_merge($condition, [$prefix . '_paint_mode' => 'gradient']),
                'selectors' => $this->build_indicator_selectors(
                    is_array($underlineSelectors) ? $underlineSelectors : [$underlineSelectors],
                    is_array($borderSelectors) ? $borderSelectors : [$borderSelectors],
                    $wrapper_class . 'gradient',
                    'gradient_angle',
                    $var_prefix
                ),
            ]
        );
    }

    /**
     * Builds the shared Elementor controls for classic and gradient paint options.
     *
     * We store the mode on the wrapper via prefix classes so each widget can switch
     * between classic and gradient output without extra runtime JS.
     *
     * @param string          $prefix    Unique control prefix.
     * @param string          $label     Visible control label.
     * @param string|string[] $selectors Target selector or selector list.
     * @param string          $type      Supported values: text, background, border.
     * @param array           $args      Optional condition/default arguments.
     */
    private function add_paint_controls(string $prefix, string $label, $selectors, string $type, array $args = []): void
    {
        $selectors = is_array($selectors) ? $selectors : [$selectors];
        $condition = $args['condition'] ?? [];
        $default_mode = $args['default_mode'] ?? 'classic';
        $wrapper_class = 'calio-' . str_replace('_', '-', sanitize_key($prefix)) . '-paint-';
        $var_prefix = '--' . str_replace('_', '-', sanitize_key($prefix)) . '-paint';

        $this->add_control(
            $prefix . '_paint_mode',
            [
                'label' => $label,
                'type' => Controls_Manager::CHOOSE,
                'options' => [
                    'classic' => [
                        'title' => esc_html__('Classic', 'calio-addon'),
                        'icon' => 'eicon-paint-brush',
                    ],
                    'gradient' => [
                        'title' => esc_html__('Gradient', 'calio-addon'),
                        'icon' => 'eicon-barcode',
                    ],
                ],
                'default' => $default_mode,
                'toggle' => false,
                'prefix_class' => $wrapper_class,
                'condition' => $condition,
            ]
        );

        $this->add_control(
            $prefix . '_color',
            [
                'label' => esc_html__('Classic Color', 'calio-addon'),
                'type' => Controls_Manager::COLOR,
                'condition' => array_merge($condition, [$prefix . '_paint_mode' => 'classic']),
                'selectors' => $this->build_paint_selectors(
                    $selectors,
                    $wrapper_class . 'classic',
                    $type,
                    'classic',
                    $var_prefix
                ),
            ]
        );

        $this->add_control(
            $prefix . '_gradient_start',
            [
                'label' => esc_html__('Gradient Start', 'calio-addon'),
                'type' => Controls_Manager::COLOR,
                'condition' => array_merge($condition, [$prefix . '_paint_mode' => 'gradient']),
                'selectors' => $this->build_paint_selectors(
                    $selectors,
                    $wrapper_class . 'gradient',
                    $type,
                    'gradient_start',
                    $var_prefix
                ),
            ]
        );

        $this->add_control(
            $prefix . '_gradient_end',
            [
                'label' => esc_html__('Gradient End', 'calio-addon'),
                'type' => Controls_Manager::COLOR,
                'condition' => array_merge($condition, [$prefix . '_paint_mode' => 'gradient']),
                'selectors' => $this->build_paint_selectors(
                    $selectors,
                    $wrapper_class . 'gradient',
                    $type,
                    'gradient_end',
                    $var_prefix
                ),
            ]
        );

        $this->add_control(
            $prefix . '_gradient_angle',
            [
                'label' => esc_html__('Gradient Angle', 'calio-addon'),
                'type' => Controls_Manager::SLIDER,
                'size_units' => ['deg'],
                'range' => [
                    'deg' => [
                        'min' => 0,
                        'max' => 360,
                    ],
                ],
                'default' => [
                    'unit' => 'deg',
                    'size' => 135,
                ],
                'condition' => array_merge($condition, [$prefix . '_paint_mode' => 'gradient']),
                'selectors' => $this->build_paint_selectors(
                    $selectors,
                    $wrapper_class . 'gradient',
                    $type,
                    'gradient_angle',
                    $var_prefix
                ),
            ]
        );
    }

    /**
     * Converts a plain widget selector into a wrapper-class-aware selector map.
     *
     * @param string[] $selectors    Original selectors.
     * @param string   $wrapperClass Wrapper class added by the paint mode control.
     * @param string   $type         Paint type.
     * @param string   $variant      classic, gradient_start, gradient_end, gradient_angle.
     * @param string   $varPrefix    CSS variable prefix.
     *
     * @return array<string, string>
     */
    private function build_paint_selectors(array $selectors, string $wrapperClass, string $type, string $variant, string $varPrefix): array
    {
        $map = [];

        foreach ($selectors as $selector) {
            $selector = $this->inject_wrapper_class((string) $selector, $wrapperClass);

            if ($type === 'text') {
                $map[$selector] = $this->build_text_paint_rule($variant, $varPrefix);
                continue;
            }

            if ($type === 'border') {
                $map[$selector] = $this->build_border_paint_rule($variant, $varPrefix);
                continue;
            }

            $map[$selector] = $this->build_background_paint_rule($variant, $varPrefix);
        }

        return $map;
    }

    /**
     * Prepends the dynamic wrapper class generated by Elementor's prefix_class setting.
     */
    private function inject_wrapper_class(string $selector, string $wrapperClass): string
    {
        if (strpos($selector, '{{WRAPPER}}') !== false) {
            return str_replace('{{WRAPPER}}', '{{WRAPPER}}.' . $wrapperClass, $selector);
        }

        return '{{WRAPPER}}.' . $wrapperClass . ' ' . ltrim($selector);
    }

    /**
     * Returns the CSS declaration set used for text paint output.
     */
    private function build_text_paint_rule(string $variant, string $varPrefix): string
    {
        if ($variant === 'classic') {
            return 'color: {{VALUE}}; background-image: none; -webkit-text-fill-color: initial;';
        }

        if ($variant === 'gradient_start') {
            return $varPrefix . '-start: {{VALUE}}; background-image: linear-gradient(var(' . $varPrefix . '-angle, 135deg), var(' . $varPrefix . '-start), var(' . $varPrefix . '-end, var(' . $varPrefix . '-start))); -webkit-background-clip: text; background-clip: text; color: transparent; -webkit-text-fill-color: transparent;';
        }

        if ($variant === 'gradient_end') {
            return $varPrefix . '-end: {{VALUE}}; background-image: linear-gradient(var(' . $varPrefix . '-angle, 135deg), var(' . $varPrefix . '-start, {{VALUE}}), var(' . $varPrefix . '-end)); -webkit-background-clip: text; background-clip: text; color: transparent; -webkit-text-fill-color: transparent;';
        }

        return $varPrefix . '-angle: {{SIZE}}{{UNIT}}; background-image: linear-gradient(var(' . $varPrefix . '-angle, 135deg), var(' . $varPrefix . '-start), var(' . $varPrefix . '-end, var(' . $varPrefix . '-start))); -webkit-background-clip: text; background-clip: text; color: transparent; -webkit-text-fill-color: transparent;';
    }

    /**
     * Returns the CSS declaration set used for background paint output.
     */
    private function build_background_paint_rule(string $variant, string $varPrefix): string
    {
        if ($variant === 'classic') {
            return 'background-color: {{VALUE}}; background-image: none;';
        }

        if ($variant === 'gradient_start') {
            return $varPrefix . '-start: {{VALUE}}; background-image: linear-gradient(var(' . $varPrefix . '-angle, 135deg), var(' . $varPrefix . '-start), var(' . $varPrefix . '-end, var(' . $varPrefix . '-start)));';
        }

        if ($variant === 'gradient_end') {
            return $varPrefix . '-end: {{VALUE}}; background-image: linear-gradient(var(' . $varPrefix . '-angle, 135deg), var(' . $varPrefix . '-start, {{VALUE}}), var(' . $varPrefix . '-end));';
        }

        return $varPrefix . '-angle: {{SIZE}}{{UNIT}}; background-image: linear-gradient(var(' . $varPrefix . '-angle, 135deg), var(' . $varPrefix . '-start), var(' . $varPrefix . '-end, var(' . $varPrefix . '-start)));';
    }

    /**
     * Returns the CSS declaration set used for border paint output.
     */
    private function build_border_paint_rule(string $variant, string $varPrefix): string
    {
        if ($variant === 'classic') {
            return 'border-color: {{VALUE}}; border-image: none;';
        }

        if ($variant === 'gradient_start') {
            return $varPrefix . '-start: {{VALUE}}; border-image-slice: 1; border-image-source: linear-gradient(var(' . $varPrefix . '-angle, 135deg), var(' . $varPrefix . '-start), var(' . $varPrefix . '-end, var(' . $varPrefix . '-start)));';
        }

        if ($variant === 'gradient_end') {
            return $varPrefix . '-end: {{VALUE}}; border-image-slice: 1; border-image-source: linear-gradient(var(' . $varPrefix . '-angle, 135deg), var(' . $varPrefix . '-start, {{VALUE}}), var(' . $varPrefix . '-end));';
        }

        return $varPrefix . '-angle: {{SIZE}}{{UNIT}}; border-image-slice: 1; border-image-source: linear-gradient(var(' . $varPrefix . '-angle, 135deg), var(' . $varPrefix . '-start), var(' . $varPrefix . '-end, var(' . $varPrefix . '-start)));';
    }

    /**
     * Builds selector rules for tab/card indicators that can be classic or gradient.
     *
     * @param string[] $underlineSelectors Underline-style selectors.
     * @param string[] $borderSelectors    Border-style selectors.
     * @param string   $wrapperClass       Wrapper class added by prefix_class.
     * @param string   $variant            classic, gradient_start, gradient_end, gradient_angle.
     * @param string   $varPrefix          CSS variable prefix.
     *
     * @return array<string, string>
     */
    private function build_indicator_selectors(
        array $underlineSelectors,
        array $borderSelectors,
        string $wrapperClass,
        string $variant,
        string $varPrefix
    ): array {
        $map = [];

        foreach ($underlineSelectors as $selector) {
            if ($selector === '') {
                continue;
            }

            $map[$this->inject_wrapper_class((string) $selector, $wrapperClass)] = $this->build_indicator_underline_rule($variant, $varPrefix);
        }

        foreach ($borderSelectors as $selector) {
            if ($selector === '') {
                continue;
            }

            $map[$this->inject_wrapper_class((string) $selector, $wrapperClass)] = $this->build_border_paint_rule($variant, $varPrefix);
        }

        return $map;
    }

    /**
     * Returns the CSS declaration set used for underline indicator paint output.
     */
    private function build_indicator_underline_rule(string $variant, string $varPrefix): string
    {
        if ($variant === 'classic') {
            return 'box-shadow: inset 0 -2px 0 0 {{VALUE}}; background-image: none;';
        }

        if ($variant === 'gradient_start') {
            return $varPrefix . '-start: {{VALUE}}; box-shadow: none; background-image: linear-gradient(var(' . $varPrefix . '-angle, 135deg), var(' . $varPrefix . '-start), var(' . $varPrefix . '-end, var(' . $varPrefix . '-start))); background-repeat: no-repeat; background-position: left bottom; background-size: 100% 2px;';
        }

        if ($variant === 'gradient_end') {
            return $varPrefix . '-end: {{VALUE}}; box-shadow: none; background-image: linear-gradient(var(' . $varPrefix . '-angle, 135deg), var(' . $varPrefix . '-start, {{VALUE}}), var(' . $varPrefix . '-end)); background-repeat: no-repeat; background-position: left bottom; background-size: 100% 2px;';
        }

        return $varPrefix . '-angle: {{SIZE}}{{UNIT}}; box-shadow: none; background-image: linear-gradient(var(' . $varPrefix . '-angle, 135deg), var(' . $varPrefix . '-start), var(' . $varPrefix . '-end, var(' . $varPrefix . '-start))); background-repeat: no-repeat; background-position: left bottom; background-size: 100% 2px;';
    }
}
