<?php

declare(strict_types=1);

namespace Builder\Core\Foundation\Settings;

use Builder\Core\Foundation\Definition\FieldBuilder;
use ReflectionClass;
use ReflectionProperty;
use Spatie\LaravelSettings\Settings;
use Spatie\LaravelSettings\SettingsRepositories\SettingsRepository;

/**
 * Abstract base class for Builder settings pages.
 *
 * Extends Spatie\LaravelSettings\Settings to provide type-safe, auto-injectable
 * settings with UI metadata in one class.
 *
 * Subclasses declare public properties for storage and implement methods for
 * the UI (sections/fields/rules/label).
 */
abstract class BuilderSettings extends Settings
{
    /**
     * URL routing key for this settings page.
     * Defaults to group(); sub-pages may override to provide a custom slug.
     */
    public function getKey(): string
    {
        return static::group();
    }

    /**
     * Human-readable label shown in the sidebar and as the page heading.
     */
    abstract public function getLabel(): string;

    /**
     * Optional icon name (Lucide icon) shown in the sidebar.
     */
    public function getIcon(): string
    {
        return '';
    }

    /**
     * Optional parent page key for sub-pages.
     * Sub-pages appear nested under their parent in the sidebar.
     */
    public function getParent(): ?string
    {
        return null;
    }

    /**
     * Priority used to sort settings pages in the sidebar (lower = higher).
     */
    public function getPriority(): int
    {
        return 50;
    }

    /**
     * Define the fields and sections for this settings page.
     */
    public function fields(): FieldBuilder
    {
        return FieldBuilder::make();
    }

    /**
     * Validation rules covering all fields on this page.
     *
     * @return array<string, mixed>
     */
    public function rules(): array
    {
        return [];
    }

    /**
     * Optional permission string. If set, users without this permission
     * will receive a 403 when accessing this settings page.
     */
    public function getPermission(): ?string
    {
        return null;
    }

    /**
     * The navigation group key this settings page is injected into.
     * Pages returning 'settings' appear as children of the Settings menu item.
     * Return an empty string to prevent auto-injection (register nav explicitly).
     */
    public function navGroup(): string
    {
        return 'settings';
    }

    /**
     * Return all settings as a key→value array for the Vue frontend.
     * Delegates to Spatie's built-in toArray().
     *
     * @return array<string, mixed>
     */
    public function settingsToArray(): array
    {
        return $this->toArray();
    }

    /**
     * Coerce null values for strictly-typed bool properties to false.
     *
     * Checkbox / toggle inputs send null when unchecked. This method converts
     * null → false for public bool (non-nullable) properties so that fill()
     * does not throw a TypeError.
     *
     * @param  array<string, mixed>  $data
     * @return array<string, mixed>
     */
    public static function coerceData(array $data): array
    {
        $reflection = new ReflectionClass(static::class);

        foreach ($data as $key => $value) {
            if ($value !== null || ! $reflection->hasProperty($key)) {
                continue;
            }

            $type = $reflection->getProperty($key)->getType();

            if ($type instanceof \ReflectionNamedType && ! $type->allowsNull() && $type->getName() === 'bool') {
                $data[$key] = false;
            }
        }

        return $data;
    }

    /**
     * Returns the names of all public properties declared on the settings class.
     * Used to scope incoming request data to only keys this class owns.
     *
     * @return list<string>
     */
    public function propertyNames(): array
    {
        return array_map(
            fn (ReflectionProperty $p): string => $p->getName(),
            (new ReflectionClass(static::class))->getProperties(ReflectionProperty::IS_PUBLIC),
        );
    }

    /**
     * Seed the initial settings rows in the database with default property values.
     *
     * Spatie requires DB rows to exist before save() can be called. This method
     * creates a row for each public property if one does not already exist.
     * Safe to call multiple times (idempotent).
     *
     * Use in install commands and test setup.
     */
    public function seedDefaults(): void
    {
        $repo = app(SettingsRepository::class);
        $group = static::group();

        foreach ((new ReflectionClass(static::class))->getProperties(ReflectionProperty::IS_PUBLIC) as $property) {
            $name = $property->getName();

            if (str_starts_with($name, '_')) {
                continue;
            }

            if ($repo->checkIfPropertyExists($group, $name)) {
                continue;
            }

            $defaultValue = $property->hasDefaultValue() ? $property->getDefaultValue() : null;
            $repo->createProperty($group, $name, $defaultValue);
        }
    }
}
