<?php

namespace GeminiLabs\SiteReviews\Modules\Html;

use GeminiLabs\SiteReviews\Contracts\FieldContract;
use GeminiLabs\SiteReviews\Database\OptionManager;
use GeminiLabs\SiteReviews\Helper;
use GeminiLabs\SiteReviews\Helpers\Arr;
use GeminiLabs\SiteReviews\Helpers\Str;
use GeminiLabs\SiteReviews\Modules\Translation;

class SettingForm extends Form
{
    protected array $groups;

    /**
     * Field dependencies are set after all fields have been added and normalized.
     */
    public function __construct(array $groups = [])
    {
        $this->groups = $groups;
        $values = glsr(OptionManager::class)->all();
        $values = array_intersect_key($values, ['settings' => []]);
        $values = Arr::flatten($values);
        parent::__construct([], $values);
        $this->normalizeDependencies();
    }

    /**
     * This builds all fields into tabbed groups but it does not build the form element.
     */
    public function build(): string
    {
        return $this->buildFields();
    }

    public function config(): array
    {
        return glsr()->settings();
    }

    public function field(string $name, array $args): FieldContract
    {
        if (str_starts_with($name, 'settings.')) {
            $parts = explode('.', $name);
            $group = count($parts) > 2 ? $parts[1] : '';
            // A host's settings mount under its own slug (settings.{hostSlug}.*)
            // but display on the addons tab (which the host relabels).
            $addon = OptionManager::addons()[$group] ?? null;
            if ($addon && $addon->isHost()) {
                $group = 'addons';
            }
            $args['group'] = $group;
        }
        return parent::field($name, $args);
    }

    public function fieldClass(): string
    {
        return SettingField::class;
    }

    protected function buildFields(): string
    {
        $fields = [];
        foreach ($this->hidden() as $field) {
            $fields[] = $field->build();
        }
        foreach ($this->groups as $group => $label) {
            $method = Helper::buildMethodName('templateDataFor', $group);
            $data = method_exists($this, $method)
                ? call_user_func([$this, $method], $group)
                : $this->templateData($group);
            $text = glsr(Template::class)->build("pages/settings/{$group}", $data);
            $fields[] = glsr(Builder::class)->div([
                'class' => 'glsr-nav-view ui-tabs-hide',
                'id' => $group,
                'text' => $text,
            ]);
        }
        $rendered = implode("\n", $fields);
        return $rendered;
    }

    /**
     * Set the data-depends attribute of each field. This is done in the form
     * because field dependencies reference other field names.
     */
    protected function normalizeDependencies(): void
    {
        foreach ($this->fields() as $field) {
            if (!$field instanceof SettingField) {
                continue; // the fields/all filter can add any field
            }
            $dependencies = [];
            foreach (Arr::consolidate($field->depends_on) as $path => $value) {
                if ($triggerField = $this->offsetGet($path)) {
                    $name = $triggerField->name;
                    $dependencies[] = compact('name', 'value');
                }
            }
            if (empty($dependencies)) {
                continue;
            }
            $field['data-depends'] = wp_json_encode($dependencies, \JSON_HEX_APOS | \JSON_HEX_QUOT);
        }
    }

    /**
     * Normalize the field with the form's session data.
     * Any normalization that is not specific to the form or session data
     * should be done in the field itself.
     */
    protected function normalizeField(FieldContract $field): void
    {
        $this->normalizeFieldChecked($field);
        $this->normalizeFieldErrors($field);
        $this->normalizeFieldId($field);
        $this->normalizeFieldIsHidden($field);
        $this->normalizeFieldValue($field);
    }

    /**
     * Prefix the field id with the form id then hash it to prevent
     * a really long value since it is based on the setting path.
     */
    protected function normalizeFieldId(FieldContract $field): void
    {
        parent::normalizeFieldId($field);
        if (empty($field->id)) {
            return;
        }
        if ($field->is_raw) {
            return;
        }
        $fieldId = Str::hash($field->id, 8);
        $fieldId = Str::prefix($fieldId, glsr()->prefix);
        $field->id = $fieldId;
    }

    /**
     * Set the is_hidden property of the field. This is done in the form
     * because value is based on other field values from the session.
     */
    protected function normalizeFieldIsHidden(FieldContract $field): void
    {
        if (!$field instanceof SettingField) {
            return; // only a setting field has dependencies
        }
        $isHidden = false;
        foreach (Arr::consolidate($field->depends_on) as $path => $expectedValue) {
            $default = Arr::get(glsr()->defaults(), $path);
            $value = $this->session->values[$path] ?? $default;
            if (!is_array($expectedValue)) {
                $isHidden = $value !== $expectedValue;
            } elseif (is_array($value)) {
                $isHidden = 0 === count(array_intersect($value, $expectedValue));
            } else {
                $isHidden = !in_array($value, $expectedValue);
            }
            if ($isHidden) {
                $field->is_hidden = true;
                return;
            }
        }
    }

    /**
     * Set the value attribute of the field from the session.
     */
    protected function normalizeFieldValue(FieldContract $field): void
    {
        $default = $field instanceof SettingField ? $field->default : null;
        $value = $this->session->values[$field->original_name] ?? $default ?? '';
        $field->value = $value;
    }

    protected function templateData(string $group): array
    {
        $fields = $this->fieldsFor($group);
        $rows = array_reduce($fields, fn ($carry, $field) => $carry.$field->build(), '');
        return [
            'context' => compact('rows'),
        ];
    }

    protected function templateDataForAddons(string $group): array
    {
        $fields = $this->fieldsFor($group);
        $results = [];
        foreach ($fields as $field) {
            // The section is the slug after the mount point — parts[2] for
            // both mounts (settings.addons.{slug}.* and settings.{hostSlug}.{slug}.*).
            $parts = explode('.', $field->original_name);
            $addon = $parts[2] ?? '';
            $results[$addon] ??= '';
            $results[$addon] .= $field->build();
        }
        ksort($results);
        $subsubsub = array_map('ucfirst', array_keys($results));
        $subsubsub = glsr()->filterArray('addon/subsubsub', $subsubsub);
        return [
            'settings' => $results,
            'subsubsub' => $subsubsub,
        ];
    }

    protected function templateDataForIntegrations(string $group): array
    {
        $fields = $this->fieldsFor($group);
        $results = [];
        foreach ($fields as $field) {
            $parts = explode('.', $field->original_name);
            $integration = $parts[2] ?? '';
            $results[$integration] ??= '';
            $results[$integration] .= $field->build();
        }
        ksort($results);
        $subsubsub = array_map('ucfirst', array_keys($results));
        $subsubsub = glsr()->filterArray('integration/subsubsub', $subsubsub);
        return [
            'settings' => $results,
            'subsubsub' => $subsubsub,
        ];
    }

    protected function templateDataForLicenses(string $group): array
    {
        $fields = $this->fieldsFor($group);
        usort($fields, fn ($a, $b) => strnatcasecmp($a->name, $b->name));
        $rows = array_reduce($fields, fn ($carry, $field) => $carry.$field->build(), '');
        return [
            'context' => compact('rows'),
        ];
    }

    protected function templateDataForStrings(string $group): array
    {
        $strings = glsr(Translation::class)->renderAll();
        $class = empty($strings) ? 'glsr-hidden' : '';
        return [
            'context' => [
                'class' => $class,
                'database_key' => OptionManager::databaseKey(),
                'strings' => $strings,
            ],
        ];
    }
}
