<?php

declare(strict_types=1);

namespace Builder\Core\Foundation\Resources;

use Builder\Core\Foundation\Contracts\BuilderAccessible;
use Builder\Core\Foundation\Contracts\Modules\BlockFieldHandler;
use Builder\Core\Foundation\Contracts\Translatable;
use Builder\Core\Foundation\Events\ResourceRecordCreated;
use Builder\Core\Foundation\Events\ResourceRecordUpdated;
use Builder\Core\Foundation\Revisions\RevisionService;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Event;

/**
 * Orchestrates the full write pipeline for resource records.
 *
 * Handles: data splitting, slug generation, block validation, hook execution,
 * translation persistence, field handler dispatch, and revision capture.
 *
 * Controllers delegate here after resolving the definition and stripping
 * permission-restricted fields.
 */
final class ResourcePersistenceService
{
    public function __construct(
        private readonly ResourceContextBuilder $contextBuilder,
        private readonly FieldHandlerRegistry $fieldHandlerRegistry,
        private readonly RevisionService $revisionService,
    ) {}

    /**
     * Create a new record from validated, permission-stripped data.
     *
     * @param  array<string, mixed>  $validatedData
     */
    public function store(
        ResourceDefinition $definition,
        array $validatedData,
        ?string $locale,
        BuilderAccessible $user,
    ): Model {
        $model = $definition->getModel();
        $record = new $model;

        $context = $this->contextBuilder->build($definition, $record, $validatedData, $locale, 'creating', $user);
        $record = $this->persistRecord($context);

        Event::dispatch(new ResourceRecordCreated($definition, $record, $user));

        return $record;
    }

    /**
     * Update an existing record from validated, permission-stripped data.
     *
     * @param  array<string, mixed>  $validatedData
     */
    public function update(
        ResourceDefinition $definition,
        Model $record,
        array $validatedData,
        ?string $locale,
        BuilderAccessible $user,
    ): Model {
        $context = $this->contextBuilder->build($definition, $record, $validatedData, $locale, 'updating', $user);
        $record = $this->persistRecord($context);

        Event::dispatch(new ResourceRecordUpdated($definition, $record, $user));

        return $record;
    }

    /**
     * Strip fields the user does not have update permission for.
     *
     * @param  array<string, mixed>  $data
     * @return array<string, mixed>
     */
    public function stripRestrictedFields(array $data, ResourceDefinition $definition, BuilderAccessible $user): array
    {
        $permissions = $user->builderPermissions();

        if (in_array('*', $permissions, true)) {
            return $data;
        }

        foreach ($definition->getAllFields() as $field) {
            $fieldPerm = $field->getUpdatePermission();

            if ($fieldPerm !== null && ! in_array($fieldPerm, $permissions, true)) {
                unset($data[$field->getName()]);
            }
        }

        foreach ($definition->getSections() as $section) {
            $sectionPerm = $section->getUpdatePermission();

            if ($sectionPerm !== null && ! in_array($sectionPerm, $permissions, true)) {
                foreach ($section->getFields() as $field) {
                    unset($data[$field->getName()]);
                }
            }
        }

        return $data;
    }

    private function persistRecord(ResourceWriteContext $context): Model
    {
        $this->validateHandlerRules($context);

        DB::transaction(function () use ($context): void {
            $context->record->fill($context->baseData);

            $this->runHooks($context, 'saving', $context->action === 'creating' ? 'creating' : 'updating');

            $context->record->save();

            $this->runHooks($context, 'saved', $context->action === 'creating' ? 'created' : 'updated');

            $this->syncTranslations($context);
            $this->syncHandlerFields($context);
        });

        $this->captureRevision($context);

        return $context->record;
    }

    private function validateHandlerRules(ResourceWriteContext $context): void
    {
        foreach ($this->fieldHandlerRegistry->all() as $componentName => $handlerInterface) {
            $handler = $this->fieldHandlerRegistry->resolve($componentName);

            if ($handler === null || ! $handler instanceof BlockFieldHandler) {
                continue;
            }

            $fields = $context->definition->getFieldDefinitionsByComponent($componentName);

            if ($fields === []) {
                continue;
            }

            $rules = $handler->rulesFor($fields, $context->requestData);

            if ($rules === []) {
                continue;
            }

            $validator = validator($context->requestData, $rules);
            $validator->setAttributeNames($handler->attributesFor($fields, $context->requestData));
            $validator->validate();
        }
    }

    private function runHooks(ResourceWriteContext $context, string ...$events): void
    {
        foreach ($context->definition->getHooks() as $hook) {
            if (in_array($hook->getEvent(), $events, true)) {
                $hook->execute($context->record, $context->requestData);
            }
        }
    }

    private function syncTranslations(ResourceWriteContext $context): void
    {
        if (! $context->locale || ! ($context->record instanceof Translatable) || empty($context->translatableData)) {
            return;
        }

        $translatableData = $this->contextBuilder->applyGenerateFrom(
            $context->definition,
            $context->translatableData,
            true,
            $context->record,
            $context->locale,
        );

        $context->record->setTranslation($context->locale, $translatableData);
    }

    private function syncHandlerFields(ResourceWriteContext $context): void
    {
        foreach ($this->fieldHandlerRegistry->all() as $componentName => $handlerInterface) {
            $handler = $this->fieldHandlerRegistry->resolve($componentName);

            if ($handler === null) {
                continue;
            }

            $fields = $context->definition->getFieldDefinitionsByComponent($componentName);

            if ($fields === []) {
                continue;
            }

            $handler->syncFields(
                $context->record,
                $fields,
                $context->requestData,
                $context->locale,
            );
        }
    }

    private function captureRevision(ResourceWriteContext $context): void
    {
        if (! $context->definition->isRevisable()) {
            return;
        }

        $userId = $context->user instanceof Model ? $context->user->getKey() : null;

        $this->revisionService->capture($context->record, $context->definition, $userId);
    }
}
