<?php

namespace BookStack\Api;

use BookStack\Entities\Models\BookChild;
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\Page;

class ApiEntityListFormatter
{
    /**
     * The list to be formatted.
     * @var Entity[]
     */
    protected array $list = [];

    /**
     * The fields to show in the formatted data.
     * Can be a plain string array item for a direct model field (If existing on model).
     * If the key is a string, with a callable value, the return value of the callable
     * will be used for the resultant value. A null return value will omit the property.
     * @var array<string|int, string|callable>
     */
    protected array $fields = [
        'id',
        'name',
        'slug',
        'book_id',
        'chapter_id',
        'draft',
        'template',
        'priority',
        'created_at',
        'updated_at',
    ];

    public function __construct(array $list)
    {
        $this->list = $list;

        // Default dynamic fields
        $this->withField('url', fn(Entity $entity) => $entity->getUrl());
    }

    /**
     * Add a field to be used in the formatter, with the property using the given
     * name and value being the return type of the given callback.
     */
    public function withField(string $property, callable $callback): self
    {
        $this->fields[$property] = $callback;
        return $this;
    }

    /**
     * Show the 'type' property in the response reflecting the entity type.
     * EG: page, chapter, bookshelf, book
     * To be included in results with non-pre-determined types.
     */
    public function withType(): self
    {
        $this->withField('type', fn(Entity $entity) => $entity->getType());
        return $this;
    }

    /**
     * Include tags in the formatted data.
     */
    public function withTags(): self
    {
        $this->withField('tags', fn(Entity $entity) => $entity->tags);
        return $this;
    }

    /**
     * Include parent book/chapter info in the formatted data.
     */
    public function withParents(): self
    {
        $this->withField('book', function (Entity $entity) {
            if ($entity instanceof BookChild && $entity->book) {
                return $entity->book->only(['id', 'name', 'slug']);
            }
            return null;
        });

        $this->withField('chapter', function (Entity $entity) {
            if ($entity instanceof Page && $entity->chapter) {
                return $entity->chapter->only(['id', 'name', 'slug']);
            }
            return null;
        });

        return $this;
    }

    /**
     * Format the data and return an array of formatted content.
     * @return array[]
     */
    public function format(): array
    {
        $results = [];

        foreach ($this->list as $item) {
            $results[] = $this->formatSingle($item);
        }

        return $results;
    }

    /**
     * Format a single entity item to a plain array.
     */
    protected function formatSingle(Entity $entity): array
    {
        $result = [];
        $values = (clone $entity)->toArray();

        foreach ($this->fields as $field => $callback) {
            if (is_string($callback)) {
                $field = $callback;
                if (!isset($values[$field])) {
                    continue;
                }
                $value = $values[$field];
            } else {
                $value = $callback($entity);
                if (is_null($value)) {
                    continue;
                }
            }

            $result[$field] = $value;
        }

        return $result;
    }
}
