<?php
/**
 * Component instance insert, update, and detach writes.
 *
 * @package LolaCoreForBricks
 */

declare(strict_types=1);

namespace LolaCoreForBricks\Handlers;

use LolaCoreForBricks\Elements\ElementTree;
use LolaCoreForBricks\Support\BricksCodeGuard;
use LolaCoreForBricks\Support\ComponentProperties;

/**
 * Keeps ComponentWriteHandler under the 600-line budget without changing its public API.
 */
trait ComponentInstanceWrites
{
    /**
     * @param array<string, mixed> $params
     * @return array<string, mixed>
     */
    public function insertComponentInstance(array $params, bool $dryRun = false): array
    {
        $guard = $this->environmentGuard($params);
        if ($guard !== null) {
            return $guard;
        }

        $resolved = $this->requireComponent($params);
        if (isset($resolved['error'])) {
            return $resolved;
        }

        $loaded = $this->loadTree($params);
        if (isset($loaded['error'])) {
            return $loaded;
        }

        /** @var ElementTree $tree */
        $tree     = $loaded['tree'];
        $position = $this->position($params);
        $reference = $this->insertReference($params, $position);
        $nesting   = $this->nestingAtPlacement($tree, $position, $reference, $resolved['component']);

        if ($nesting['over_limit']) {
            return $this->failure(
                'That nest would go deeper than Bricks allows (10). I left the page as it is.',
                'component_depth_limit'
            );
        }

        $rootName = $this->componentRootName($resolved['component']);
        $values   = ComponentProperties::filterInstanceValues(
            is_array($resolved['component']['properties'] ?? null) ? $resolved['component']['properties'] : [],
            is_array($params['properties'] ?? null) ? $params['properties'] : []
        );

        $instance = [
            'id'         => $this->ids()->generate(array_merge($tree->ids(), $this->components()->existingIds())),
            'name'       => $rootName,
            'parent'     => ElementTree::ROOT_PARENT,
            'children'   => [],
            'settings'   => [],
            'cid'        => $resolved['id'],
            'properties' => $values,
        ];

        $error = $tree->insert($instance, $position, $reference);
        if ($error !== null) {
            return $this->treeFailure($error);
        }

        if ($dryRun) {
            return [
                'success'      => true,
                'dry_run'      => true,
                'message'      => $nesting['at_limit']
                    ? 'This sits at the Bricks nesting limit (10). Deeper nests will not expand.'
                    : 'Insert preview is ready.',
                'component_id' => $resolved['id'],
                'instance_id'  => $instance['id'],
                'nesting'      => $nesting,
            ];
        }

        $written = $this->elements()->write($loaded['post_id'], $loaded['area'], $tree->toList(), false);
        if (($written['success'] ?? false) !== true) {
            return $written;
        }

        $this->components()->syncIndex([$loaded['post_id']]);

        $result = [
            'success'      => true,
            'message'      => 'I inserted an instance. Refresh the front to see it.',
            'component_id' => $resolved['id'],
            'instance_id'  => $instance['id'],
            'post_id'      => $loaded['post_id'],
            'area'         => $loaded['area'],
            'nesting'      => $nesting,
            'impact'       => $this->components()->instanceImpact($resolved['id']),
        ];

        if ($nesting['at_limit']) {
            $result['warning'] = 'This sits at the Bricks nesting limit (10). Deeper nests will not expand.';
        }

        return $result;
    }

    /**
     * @param array<string, mixed> $params
     * @return array<string, mixed>
     */
    public function updateComponentInstance(array $params, bool $dryRun = false): array
    {
        $guard = $this->environmentGuard($params);
        if ($guard !== null) {
            return $guard;
        }

        $loaded = $this->loadTree($params);
        if (isset($loaded['error'])) {
            return $loaded;
        }

        $instanceId = (string) ($params['element_id'] ?? $params['instance_id'] ?? '');
        if ($instanceId === '') {
            return $this->failure('I need the instance to update. Which one?', 'missing_element');
        }

        /** @var ElementTree $tree */
        $tree     = $loaded['tree'];
        $instance = $tree->findById($instanceId);
        if ($instance === null) {
            return $this->failure('I could not find that instance on this page.', 'element_not_found');
        }

        $cid = (string) ($instance['cid'] ?? '');
        if ($cid === '') {
            return $this->failure('That element is not a component instance.', 'not_an_instance');
        }

        $component = $this->components()->findRaw($cid);
        if ($component === null) {
            return $this->failure('I could not find that component in the library.', 'component_not_found');
        }

        $values = ComponentProperties::filterInstanceValues(
            is_array($component['properties'] ?? null) ? $component['properties'] : [],
            is_array($params['properties'] ?? null) ? $params['properties'] : []
        );

        $current = is_array($instance['properties'] ?? null) ? $instance['properties'] : [];
        $merged  = array_merge($current, $values);

        $list = $tree->toList();
        foreach ($list as $index => $element) {
            if ((string) ($element['id'] ?? '') !== $instanceId) {
                continue;
            }

            $list[$index]['properties'] = $merged;
            break;
        }

        if ($dryRun) {
            return [
                'success'      => true,
                'dry_run'      => true,
                'message'      => 'Instance preview is ready.',
                'component_id' => $cid,
                'instance_id'  => $instanceId,
                'properties'   => $merged,
            ];
        }

        $written = $this->elements()->write($loaded['post_id'], $loaded['area'], $list, false);
        if (($written['success'] ?? false) !== true) {
            return $written;
        }

        $this->components()->syncIndex([$loaded['post_id']]);

        return [
            'success'      => true,
            'message'      => 'I updated that instance. The other instances keep their own values.',
            'component_id' => $cid,
            'instance_id'  => $instanceId,
            'post_id'      => $loaded['post_id'],
            'area'         => $loaded['area'],
            'properties'   => $merged,
        ];
    }

    /**
     * @param array<string, mixed> $params
     * @return array<string, mixed>
     */
    public function detachComponentInstance(array $params, bool $dryRun = false): array
    {
        $guard = $this->environmentGuard($params);
        if ($guard !== null) {
            return $guard;
        }

        $loaded = $this->loadTree($params);
        if (isset($loaded['error'])) {
            return $loaded;
        }

        $instanceId = (string) ($params['element_id'] ?? $params['instance_id'] ?? '');
        if ($instanceId === '') {
            return $this->failure('I need the instance to detach. Which one?', 'missing_element');
        }

        /** @var ElementTree $tree */
        $tree     = $loaded['tree'];
        $instance = $tree->findById($instanceId);
        if ($instance === null) {
            return $this->failure('I could not find that instance on this page.', 'element_not_found');
        }

        $cid = (string) ($instance['cid'] ?? '');
        if ($cid === '') {
            return $this->failure('That element is not a component instance.', 'not_an_instance');
        }

        $component = $this->components()->findRaw($cid);
        if ($component === null) {
            return $this->failure('I could not find that component in the library.', 'component_not_found');
        }

        $elements = is_array($component['elements'] ?? null) ? $component['elements'] : [];
        if ($elements === []) {
            return $this->failure('That component has no elements to detach.', 'empty_component');
        }

        $codeGuard = BricksCodeGuard::rejectionResponse($elements);
        if ($codeGuard !== null) {
            return $codeGuard;
        }

        $taken    = array_merge($tree->ids(), $this->components()->existingIds());
        $remapped = ElementTree::fromList($elements)->withRemappedIds($this->ids());
        $branch   = ElementTree::fromList($remapped);
        $root     = $branch->findById((string) ($remapped[0]['id'] ?? ''));
        foreach ($remapped as $element) {
            if (($element['parent'] ?? ElementTree::ROOT_PARENT) === ElementTree::ROOT_PARENT
                || (string) ($element['parent'] ?? '') === '0'
                || (string) ($element['id'] ?? '') === $cid
            ) {
                $root = $element;
                break;
            }
        }

        if ($root === null) {
            return $this->failure('I could not find the component root to detach.', 'missing_component_root');
        }

        unset($root['cid']);
        $rootId = (string) ($root['id'] ?? '');

        if ($dryRun) {
            return [
                'success'      => true,
                'dry_run'      => true,
                'message'      => 'Detach preview is ready.',
                'component_id' => $cid,
                'instance_id'  => $instanceId,
                'new_root_id'  => $rootId,
                'element_count'=> count($remapped),
            ];
        }

        $parentId = (string) (($instance['parent'] ?? '') === ElementTree::ROOT_PARENT || ($instance['parent'] ?? '') === 0
            ? ''
            : $instance['parent']);
        $siblings = [];
        if ($parentId !== '') {
            $parent   = $tree->findById($parentId);
            $siblings = is_array($parent['children'] ?? null) ? $parent['children'] : [];
        }

        $at = array_search($instanceId, array_map('strval', $siblings), true);
        $tree->delete($instanceId);

        $position  = ElementTree::POSITION_CHILD;
        $reference = $parentId;
        if ($at !== false && $at > 0 && isset($siblings[$at - 1]) && $tree->findById((string) $siblings[$at - 1]) !== null) {
            $position  = ElementTree::POSITION_AFTER;
            $reference = (string) $siblings[$at - 1];
        }

        $normalized = [];
        foreach ($remapped as $element) {
            if ((string) ($element['id'] ?? '') === $rootId) {
                unset($element['cid']);
                $element['parent'] = ElementTree::ROOT_PARENT;
                $normalized[]      = $element;
                continue;
            }
            $normalized[] = $element;
        }

        $error = $tree->insertBranch($normalized, $position, $reference);
        if ($error !== null) {
            return $this->treeFailure($error);
        }

        $written = $this->elements()->write($loaded['post_id'], $loaded['area'], $tree->toList(), false);
        if (($written['success'] ?? false) !== true) {
            return $written;
        }

        $this->components()->syncIndex([$loaded['post_id']]);

        return [
            'success'       => true,
            'message'       => 'I detached that instance. It is now regular elements on this page only.',
            'component_id'  => $cid,
            'instance_id'   => $instanceId,
            'new_root_id'   => $rootId,
            'post_id'       => $loaded['post_id'],
            'area'          => $loaded['area'],
            'element_count' => count($normalized),
        ];
    }

}
