<?php

namespace App\Domains\Tenant\Services;

use App\Domains\Attachable\Models\Attachment;
use App\Domains\Tenant\Models\Tenant;
use App\Domains\Tenant\Models\WebsiteExtra;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Support\Collection;

class TenantInformationService
{
    private const DEFAULT_RELATIONS = [
        'headerLogo',
        'footerLogo',
        'favicon',
        'websiteType.favicon',
        'headerBackgroundImage',
        'footerBackgroundImage',
        'socialMedia',
        'websiteExtras.websiteTypeExtra',
        'eventDates',
    ];

    /**
     * @param  array<int|string, mixed>  $relations
     */
    public function load(Tenant $tenant, array $relations = []): Tenant
    {
        $this->loadMany(new EloquentCollection([$tenant]), $relations);

        return $tenant;
    }

    /**
     * @param  EloquentCollection<int, Tenant>  $tenants
     * @param  array<int|string, mixed>  $relations
     * @return EloquentCollection<int, Tenant>
     */
    public function loadMany(
        EloquentCollection $tenants,
        array $relations = []
    ): EloquentCollection {
        $tenants->loadMissing([
            ...self::DEFAULT_RELATIONS,
            ...$relations,
        ]);

        $websiteExtras = $tenants
            ->flatMap(fn (Tenant $tenant): Collection => $tenant->websiteExtras);

        $this->resolveWebsiteExtraAttachments($websiteExtras);

        return $tenants;
    }

    /**
     * Replace attachment IDs with their models in each extra's resolved config.
     *
     * @param  Collection<int, WebsiteExtra>  $websiteExtras
     */
    private function resolveWebsiteExtraAttachments(Collection $websiteExtras): void
    {
        $attachmentIds = $websiteExtras
            ->flatMap(function (WebsiteExtra $extra): array {
                $ids = [];

                foreach ($this->attachmentTransformPaths($extra) as $path) {
                    $ids = [
                        ...$ids,
                        ...$this->valuesAtPath($extra->config, $this->pathSegments($path)),
                    ];
                }

                return $ids;
            })
            ->filter(fn (mixed $id): bool => is_int($id) || ctype_digit((string) $id))
            ->map(fn (mixed $id): int => (int) $id)
            ->unique()
            ->values();

        $attachments = Attachment::query()
            ->whereIn('id', $attachmentIds)
            ->with('cropVariants.croppedAttachment')
            ->get()
            ->keyBy('id');

        foreach ($websiteExtras as $extra) {
            $config = $extra->config;

            foreach ($this->attachmentTransformPaths($extra) as $path) {
                $config = $this->replaceAtPath(
                    $config,
                    $this->pathSegments($path),
                    fn (mixed $attachmentId): ?Attachment => $attachments->get((int) $attachmentId)
                );
            }

            $extra->setResolvedConfig($config);
        }
    }

    /**
     * @return array<int, string>
     */
    private function attachmentTransformPaths(WebsiteExtra $extra): array
    {
        return collect($extra->websiteTypeExtra->config_schema['transforms'] ?? [])
            ->filter(
                fn (mixed $transform): bool => is_array($transform)
                    && ($transform['handler'] ?? null) === 'attachment'
            )
            ->keys()
            ->all();
    }

    /**
     * @return array<int, string>
     */
    private function pathSegments(string $path): array
    {
        $path = ltrim($path, '$');
        $path = ltrim($path, '.');

        return $path === '' ? [] : explode('.', $path);
    }

    /**
     * @return array<int, mixed>
     */
    private function valuesAtPath(mixed $value, array $segments): array
    {
        if ($segments === []) {
            return [$value];
        }

        if (! is_array($value)) {
            return [];
        }

        $segment = array_shift($segments);

        if ($segment === '*') {
            return collect($value)
                ->flatMap(fn (mixed $item): array => $this->valuesAtPath($item, $segments))
                ->all();
        }

        if (! array_key_exists($segment, $value)) {
            return [];
        }

        return $this->valuesAtPath($value[$segment], $segments);
    }

    private function replaceAtPath(mixed $value, array $segments, callable $replace): mixed
    {
        if ($segments === []) {
            return $value === null ? null : $replace($value);
        }

        if (! is_array($value)) {
            return $value;
        }

        $segment = array_shift($segments);

        if ($segment === '*') {
            foreach ($value as $key => $item) {
                $value[$key] = $this->replaceAtPath($item, $segments, $replace);
            }

            return $value;
        }

        if (array_key_exists($segment, $value)) {
            $value[$segment] = $this->replaceAtPath($value[$segment], $segments, $replace);
        }

        return $value;
    }
}
