<?php

declare(strict_types=1);

namespace Builder\Core\Foundation\Resources;

use Builder\Core\Foundation\Definition\FieldBuilder;
use Illuminate\Database\Eloquent\Model;

abstract class NestedResource
{
    /** FK column on child table pointing to parent (e.g. 'menu_id'). */
    abstract public static function parentField(): string;

    /** Column used for ordering (null = not sortable). */
    public static function sortField(): ?string
    {
        return null;
    }

    /** Whether nesting/tree structure is supported (parent_id column). */
    public static function isNestable(): bool
    {
        return false;
    }

    abstract public function fields(): FieldBuilder;

    /** @return array<string, mixed> */
    public function rules(string $action): array
    {
        return [];
    }

    /** @return class-string<Model> */
    abstract public static function getModel(): string;

    /**
     * Relations to eager load when fetching child records.
     *
     * @return list<string>
     */
    public function eagerLoads(): array
    {
        return [];
    }

    /**
     * Serialize a child model to an array for the frontend.
     *
     * @return array<string, mixed>
     */
    public function serializeRecord(Model $record): array
    {
        return $record->toArray();
    }

    /**
     * Load all child records for a parent (for the edit form).
     *
     * @return list<array<string, mixed>>
     */
    public function loadForParent(Model $parent): array
    {
        $model = static::getModel();
        $query = $model::query()
            ->where(static::parentField(), $parent->getKey())
            ->with($this->eagerLoads());

        if (static::sortField() !== null) {
            $query->orderBy(static::sortField());
        }

        /** @var list<array<string, mixed>> */
        return $query->get()
            ->map(fn (Model $r): array => $this->serializeRecord($r))
            ->values()
            ->toArray();
    }

    /**
     * Sync child records for a parent from form submission data.
     *
     * @param  list<array<string, mixed>>  $items
     */
    public function syncRecords(Model $parent, array $items): void
    {
        $model = static::getModel();
        $incoming = collect($items)->pluck('id')->filter()->values()->all();

        $model::query()
            ->where(static::parentField(), $parent->getKey())
            ->whereNotIn('id', $incoming)
            ->delete();

        foreach ($items as $index => $item) {
            /** @var Model $record */
            $record = isset($item['id'])
                ? ($model::query()->find($item['id']) ?? new $model)
                : new $model;

            $this->fillRecord($record, $item, $parent, $index);
            $record->save();
            $this->syncTranslations($record, $item);
        }
    }

    /**
     * Fill a child record from submitted data.
     *
     * @param  array<string, mixed>  $data
     */
    protected function fillRecord(Model $record, array $data, Model $parent, int $index): void
    {
        $record->fill([static::parentField() => $parent->getKey()]);

        if (static::sortField() !== null) {
            $record->{static::sortField()} = $data[static::sortField()] ?? $index;
        }
    }

    /**
     * Sync translations for a child record. Default: no-op.
     * Subclasses with HasTranslations should override this.
     *
     * @param  array<string, mixed>  $data
     */
    protected function syncTranslations(Model $record, array $data): void
    {
        //
    }
}
