<?php

declare(strict_types=1);

namespace Bnomei;

use Exception;
use Kirby\Cms\File;
use Kirby\Cms\Page;
use Kirby\Cms\Site;
use Kirby\Data\Yaml;
use Kirby\Toolkit\A;

final class AutoIDProcess
{
    private $object;
    private $overwrite;
    private $hasChanges;
    private $update;
    private $autoids;
    private $indexed;

    public function __construct($object, bool $overwrite = false)
    {
        if (is_a($object, Page::class)) {
            foreach ($object->files() as $file) {
                new self($file, $overwrite);
            }
        }

        $this->object = $object;
        $this->overwrite = $overwrite;
        $this->hasChanges = false;
        $this->indexed = false;
        $this->update = [];
        $this->autoids = [];

        if (! $this->overwrite) {
            // skip without reading the autoid from content file
            $diruri = null;
            if(is_a($object, Page::class)) {
                $diruri = $this->object->diruri();
            } elseif(is_a($object, File::class)) {
                $diruri = $this->object->parent()->diruri() . '@' . $this->object->filename();
            } elseif(is_a($object, Site::class)) {
                $diruri = '$';
            }

            $this->indexed = AutoIDDatabase::singleton()->findByDiruri($diruri) !== null;
        }

        if (! $this->indexed) {
            $this->indexPageOrFile();
            $this->indexStructures();
            $this->update();
        }
    }

    public function isIndexed(): bool
    {
        return $this->indexed;
    }

    private function indexPageOrFile(): void
    {
        $autoid = null;

        if ($this->object->{AutoID::FIELDNAME}()->isNotEmpty()) {
            $autoid = $this->object->{AutoID::FIELDNAME}()->value();
        }

        if ($this->overwrite || $this->object->{AutoID::FIELDNAME}()->isEmpty()) {
            $autoid = AutoID::generate();
            if (! $autoid) {
                return;
            }
            if ($this->object->blueprint()->field(AutoID::FIELDNAME)) {
                $this->update[AutoID::FIELDNAME] = $autoid;
                $this->autoids[] = $autoid;
                $this->hasChanges = true;
            } else {
                $autoid = null;
            }
        }

        if ($autoid) {
            AutoIDDatabase::singleton()->insertOrUpdate(
                $this->createItem($autoid)
            );
        }
    }

    private function indexStructures(): void
    {
        $data = [];

        foreach ($this->object->blueprint()->fields() as $field) {
            try {
                if (A::get($field, 'type') !== 'structure') {
                    continue;
                }
                $fieldname = A::get($field, 'name');
                if (! $fieldname) {
                    continue;
                }
                $field = $this->object->{$fieldname}();
                if ($field->isEmpty()) {
                    continue;
                }

                $yaml = Yaml::decode($field->value());
                $yaml = $this->indexArray($yaml, [$fieldname]);
                $data[$fieldname] = Yaml::encode($yaml);
            } catch (Exception $exception) {
            }
        }

        $this->update = array_merge($this->update, $data);
    }

    private function indexArray(array $data, array $tree = []): array
    {
        $copy = $data;
        foreach ($data as $key => $value) {
            if ($key === AutoID::FIELDNAME) {
                $autoid = $value;
                if ($this->overwrite || ! $value || $value === 'null') {
                    $autoid = AutoID::generate();
                    if ($autoid) {
                        $copy[$key] = $autoid;
                        $this->autoids[] = $autoid;
                        $this->hasChanges = true;
                    }
                }

                AutoIDDatabase::singleton()->insertOrUpdate(
                    $this->createItem($autoid, $tree)
                );
            } elseif (is_array($value)) {
                $next = $tree; // copy
                $next[] = $key;
                $copy[$key] = $this->indexArray($value, $next);
            }
        }
        return $copy;
    }

    private function update(): void
    {
        if (! $this->hasChanges) {
            $this->indexed = true;
            return;
        }

        try {
            kirby()->impersonate('kirby');
            // use save() not update() to avoid hooks
            $this->object->save($this->update);
        } catch (Exception $exception) {
            $this->revert();
        } finally {
            $this->indexed = true;
        }
    }

    private function revert(): void
    {
        foreach ($this->autoids as $autoid) {
            AutoIDDatabase::singleton()->delete($autoid);
        }
        $this->autoids = [];
    }

    private function createItem(string $autoid, array $tree = null): AutoIDItem
    {
        if (is_array($tree) && (
                is_a($this->object, Page::class) ||
                is_a($this->object, File::class) ||
                is_a($this->object, Site::class)
            )
        ) {
            $data = $this->itemFromStructureObject($this->object, $tree);
        } elseif (is_a($this->object, Page::class)) {
            $data = $this->itemFromPage($this->object);
        } elseif (is_a($this->object, Site::class)) {
            $data = $this->itemFromSite($this->object);
        } elseif (is_a($this->object, File::class)) {
            $data = $this->itemFromFile($this->object);
        }

        return new AutoIDItem(array_merge($data, [
            'autoid' => $autoid,
        ]));
    }

    private function itemFromPage($object): array
    {
        return [
            'page' => $object->id(),
            'diruri' => $object->diruri(),
            'modified' => $object->modified(),
            'kind' => AutoIDItem::KIND_PAGE,
            'template' => (string) $object->intendedTemplate(),
        ];
    }

    private function itemFromSite($object): array
    {
        return [
            'page' => '$',
            'diruri' => '$',
            'modified' => filemtime($object->contentFile()), // just site not ALL
            'kind' => AutoIDItem::KIND_PAGE,
            'template' => 'site',
        ];
    }

    private function itemFromFile($object): array
    {
        return [
            'page' => $object->parent()->id(),
            'diruri' => $object->parent()->diruri() . '@' . $object->filename(),
            'filename' => $object->filename(),
            'modified' => $object->modified(),
            'kind' => AutoIDItem::KIND_FILE,
            'template' => (string) $object->template(),
        ];
    }

    private function itemFromStructureObject($object, array $tree): array
    {
        $id = is_a($this->object, Site::class) ? '$' : $object->id();
        $diruri = is_a($this->object, Site::class) ? '$' : $object->diruri();
        $treeFlat = implode(',', $tree);
        $modified = is_a($this->object, Site::class) ?
            filemtime($object->contentFile()) :
            $object->modified();
        return [
            'page' => $id,
            'diruri' => $diruri . '#' . $treeFlat,
            'modified' => $modified,
            'structure' => $treeFlat,
            'kind' => AutoIDItem::KIND_STRUCTUREOBJECT,
            'template' => 'structureobject',
        ];
    }
}
