<?php
declare(strict_types=1);

namespace PpPhp\Core\Designer\DragDrop;

use PpPhp\Core\Designer\Component\DesignerComponentRuntime;

final class DesignerDragDropRuntime
{
    private DesignerComponentRuntime $components;
    private DesignerDragDropHistory $history;
    private DesignerDragDropClipboard $clipboard;
    private array $operations = [];

    public function __construct(?DesignerComponentRuntime $components = null)
    {
        $this->components = $components ?? new DesignerComponentRuntime();
        $this->history = new DesignerDragDropHistory();
        $this->clipboard = new DesignerDragDropClipboard();
    }

    public function insert(array $page, DesignerDragPayload $payload, DesignerDropTarget $target): DesignerDropResult
    {
        $before = $page;

        $component = $payload->component();
        if ($component === []) {
            $component = $this->components->create($payload->type(), $this->makeId($payload->type()));
        }

        $page = $this->insertComponent($page, $component, $target);
        $this->history->record($before, $page, 'insert');
        $this->operations[] = DesignerDragOperation::create('insert', $payload->toArray(), $target->toArray())->toArray();

        return DesignerDropResult::success($page, 'insert', (string)($component['id'] ?? ''));
    }

    public function move(array $page, string $componentId, DesignerDropTarget $target): DesignerDropResult
    {
        $before = $page;
        [$page, $component] = $this->removeComponent($page, $componentId);

        if ($component === null) {
            return DesignerDropResult::failure($before, 'move', 'Component not found: ' . $componentId);
        }

        $page = $this->insertComponent($page, $component, $target);
        $this->history->record($before, $page, 'move');
        $this->operations[] = DesignerDragOperation::create('move', ['componentId' => $componentId], $target->toArray())->toArray();

        return DesignerDropResult::success($page, 'move', $componentId);
    }

    public function duplicate(array $page, string $componentId, DesignerDropTarget $target): DesignerDropResult
    {
        $component = $this->findComponent($page, $componentId);
        if ($component === null) {
            return DesignerDropResult::failure($page, 'duplicate', 'Component not found: ' . $componentId);
        }

        $copy = $this->cloneComponent($component);
        $before = $page;
        $page = $this->insertComponent($page, $copy, $target);
        $this->history->record($before, $page, 'duplicate');
        $this->operations[] = DesignerDragOperation::create('duplicate', ['componentId' => $componentId], $target->toArray())->toArray();

        return DesignerDropResult::success($page, 'duplicate', (string)$copy['id']);
    }

    public function copy(array $component): array
    {
        return $this->clipboard->copy($component);
    }

    public function paste(array $page, DesignerDropTarget $target): DesignerDropResult
    {
        $component = $this->clipboard->paste();
        if ($component === null) {
            return DesignerDropResult::failure($page, 'paste', 'Clipboard is empty');
        }

        $before = $page;
        $page = $this->insertComponent($page, $component, $target);
        $this->history->record($before, $page, 'paste');
        $this->operations[] = DesignerDragOperation::create('paste', ['componentId' => $component['id'] ?? ''], $target->toArray())->toArray();

        return DesignerDropResult::success($page, 'paste', (string)$component['id']);
    }

    public function undo(array $page): array
    {
        return $this->history->undo($page);
    }

    public function redo(array $page): array
    {
        return $this->history->redo($page);
    }

    public function operations(): array
    {
        return $this->operations;
    }

    public function stats(): array
    {
        return [
            'operations' => count($this->operations),
            'insertions' => count(array_filter($this->operations, static fn(array $op): bool => ($op['operation'] ?? '') === 'insert')),
            'moves' => count(array_filter($this->operations, static fn(array $op): bool => ($op['operation'] ?? '') === 'move')),
            'duplicates' => count(array_filter($this->operations, static fn(array $op): bool => ($op['operation'] ?? '') === 'duplicate')),
            'history' => $this->history->stats(),
        ];
    }

    private function insertComponent(array $page, array $component, DesignerDropTarget $target): array
    {
        $path = $target->path();
        if ($path === ['components'] || $path === []) {
            $page['components'] = array_values((array)($page['components'] ?? []));
            $index = $target->index();
            if ($index === null || $index < 0 || $index > count($page['components'])) {
                $page['components'][] = $component;
            } else {
                array_splice($page['components'], $index, 0, [$component]);
            }
            return $page;
        }

        $this->insertAtPath($page, $path, $component, $target->index());
        return $page;
    }

    private function insertAtPath(array &$root, array $path, array $component, ?int $index): void
    {
        $ref =& $root;
        foreach ($path as $segment) {
            if (is_int($segment) || ctype_digit((string)$segment)) {
                $segment = (int)$segment;
            }
            if (!isset($ref[$segment])) {
                $ref[$segment] = [];
            }
            $ref =& $ref[$segment];
        }

        if (!is_array($ref)) {
            $ref = [];
        }

        $ref = array_values($ref);
        if ($index === null || $index < 0 || $index > count($ref)) {
            $ref[] = $component;
        } else {
            array_splice($ref, $index, 0, [$component]);
        }
    }

    private function removeComponent(array $page, string $componentId): array
    {
        $component = null;
        $page['components'] = $this->removeFromList((array)($page['components'] ?? []), $componentId, $component);
        return [$page, $component];
    }

    private function removeFromList(array $components, string $componentId, ?array &$removed): array
    {
        $result = [];
        foreach ($components as $component) {
            if (!is_array($component)) {
                continue;
            }

            if (($component['id'] ?? '') === $componentId) {
                $removed = $component;
                continue;
            }

            if (isset($component['children']) && is_array($component['children'])) {
                $component['children'] = $this->removeFromList($component['children'], $componentId, $removed);
            }

            if (isset($component['props']['columns']) && is_array($component['props']['columns'])) {
                foreach ($component['props']['columns'] as $i => $column) {
                    if (isset($column['components']) && is_array($column['components'])) {
                        $component['props']['columns'][$i]['components'] = $this->removeFromList($column['components'], $componentId, $removed);
                    }
                }
                $component['props']['cells'] = $component['props']['columns'];
            }

            $result[] = $component;
        }

        return $result;
    }

    private function findComponent(array $page, string $componentId): ?array
    {
        return $this->findInList((array)($page['components'] ?? []), $componentId);
    }

    private function findInList(array $components, string $componentId): ?array
    {
        foreach ($components as $component) {
            if (!is_array($component)) {
                continue;
            }
            if (($component['id'] ?? '') === $componentId) {
                return $component;
            }
            $found = $this->findInList((array)($component['children'] ?? []), $componentId);
            if ($found !== null) {
                return $found;
            }
        }
        return null;
    }

    private function cloneComponent(array $component): array
    {
        $component['id'] = ((string)($component['id'] ?? 'component')) . '_copy_1';
        $component['name'] = $component['id'];
        $component['meta'] = array_replace((array)($component['meta'] ?? []), [
            'duplicatedAt' => date('c'),
        ]);
        return $component;
    }

    private function makeId(string $type): string
    {
        return strtolower(preg_replace('/[^a-z0-9_]+/i', '_', $type)) . '_' . substr(hash('sha256', $type . microtime(true)), 0, 8);
    }
}
