<?php

declare(strict_types=1);

namespace Builder\Core\Foundation\Settings;

use Builder\Core\Foundation\Definition\Section;

/**
 * Immutable value object that represents a fully-resolved settings page.
 *
 * The $compositeKey is the full routing key used for URL resolution.
 * For top-level pages it equals the page's own key (e.g. 'general').
 * For sub-pages it is 'parent.key' (e.g. 'general.advanced').
 */
final class SettingsDefinition
{
    /**
     * @param  list<Section>  $sections
     * @param  array<string, mixed>  $rules
     * @param  list<SettingsExtension>  $extensions
     */
    private function __construct(
        private readonly BuilderSettings $page,
        private readonly string $compositeKey,
        private readonly array $sections,
        private readonly array $rules,
        private readonly array $extensions,
    ) {}

    /**
     * @param  string  $compositeKey  Full key used for settings storage and URL routing.
     * @param  list<SettingsExtension>  $extensions
     */
    public static function make(BuilderSettings $page, string $compositeKey = '', array $extensions = []): self
    {
        if ($compositeKey === '') {
            $compositeKey = $page->getParent() !== null
                ? "{$page->getParent()}.{$page->getKey()}"
                : $page->getKey();
        }

        $sections = $page->fields()->toSections();
        $rules = $page->rules();

        foreach ($extensions as $extension) {
            $sections = array_merge($sections, $extension->fields()->toSections());
            $rules = array_merge($rules, $extension->rules());
        }

        usort($sections, fn (Section $a, Section $b): int => $a->getPriority() <=> $b->getPriority());

        return new self(
            page: $page,
            compositeKey: $compositeKey,
            sections: $sections,
            rules: $rules,
            extensions: $extensions,
        );
    }

    public function getPage(): BuilderSettings
    {
        return $this->page;
    }

    /**
     * Returns the FQCN of the settings class for container resolution.
     *
     * @return class-string<BuilderSettings>
     */
    public function getSettingsClass(): string
    {
        return $this->page::class;
    }

    public function getCompositeKey(): string
    {
        return $this->compositeKey;
    }

    /** @return list<Section> */
    public function getSections(): array
    {
        return $this->sections;
    }

    /** @return array<string, mixed> */
    public function getRules(): array
    {
        return $this->rules;
    }

    /** @return list<SettingsExtension> */
    public function getExtensions(): array
    {
        return $this->extensions;
    }

    /** @return array<string, mixed> */
    public function toArray(): array
    {
        return [
            'key' => $this->compositeKey,
            'label' => $this->page->getLabel(),
            'icon' => $this->page->getIcon(),
            'parent' => $this->page->getParent(),
            'sections' => array_map(fn (Section $s): array => $this->sectionToArray($s), $this->sections),
        ];
    }

    /** @return array<string, mixed> */
    private function sectionToArray(Section $s): array
    {
        $data = $s->toArray();

        /** @var list<array<string, mixed>> $fields */
        $fields = $data['fields'];

        $data['fields'] = array_map(fn (array $field): array => $this->injectRequiredRule($field), $fields);

        return $data;
    }

    /**
     * Adds 'required' to a field's rules array when the settings page declares
     * it as required. This allows the frontend to show the * indicator without
     * requiring every settings page to duplicate the rule on the Field object.
     *
     * @param  array<string, mixed>  $field
     * @return array<string, mixed>
     */
    private function injectRequiredRule(array $field): array
    {
        $pageRules = $this->rules[$field['name']] ?? [];

        if (
            is_array($pageRules)
            && in_array('required', $pageRules, true)
            && ! in_array('required', (array) $field['rules'], true)
        ) {
            $field['rules'][] = 'required';
        }

        return $field;
    }
}
