<?php

namespace App\Letter;

use App\Member\Member;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;

class DocumentFactory
{
    /**
     * @var array<int, class-string<Letter>>
     */
    public array $types = [
        BillDocument::class,
        RememberDocument::class,
    ];

    /**
     * @return Collection<int, Letter>
     */
    public function getTypes(): Collection
    {
        /** @var array<int, Member> */
        $emptyMembers = [];

        return collect(array_map(fn ($classString) => new $classString(collect($emptyMembers)), $this->types));
    }

    /**
     * @param class-string<Letter> $type
     */
    public function fromSingleRequest(string $type, Member $member): ?Letter
    {
        $members = $this->singleMemberPages($member, $type);

        if ($members->isEmpty()) {
            return null;
        }

        return tap(
            $this->resolve($type, $members),
            fn ($repo) => $repo->setFilename(Str::slug("{$repo->getSubject()} für {$members->first()->singleName}"))
        );
    }

    /**
     * @param class-string<Letter> $type
     */
    public function forAll(string $type, BillKind $billKind): ?Letter
    {
        $pages = $this->allMemberPages($type, $billKind);

        if ($pages->isEmpty()) {
            return null;
        }

        return tap($this->resolve($type, $pages), fn ($repo) => $repo->setFilename('alle-rechnungen'));
    }

    /**
     * @param class-string<Letter> $type
     */
    public function afterAll(string $type, BillKind $billKind): void
    {
        $letter = $this->forAll($type, $billKind);
        $this->afterSingle($letter);
    }

    /**
     * @param class-string<Letter> $type
     *
     * @return Collection<int, Letter>
     */
    public function letterCollection(string $type, BillKind $billKind): Collection
    {
        $pages = $this->allMemberPages($type, $billKind);

        return $pages->map(fn ($page) => $this->resolve($type, collect([$page])));
    }

    public function afterSingle(Letter $letter): void
    {
        foreach ($letter->allPayments() as $payment) {
            $letter->afterSingle($payment);
        }
    }

    /**
     * @param class-string<Letter> $type
     *
     * @return Collection<int, Page>
     */
    private function singleMemberPages(Member $member, string $type): Collection
    {
        $members = Member::where($member->only(['lastname', 'address', 'zip', 'location']))
            ->with([
                'payments' => fn ($query) => $type::paymentsQuery($query)
                    ->orderByRaw('nr, member_id'),
            ])
            ->get()
            ->filter(fn (Member $member) => $member->payments->count() > 0);

        return $this->toPages($members);
    }

    /**
     * @param class-string<Letter> $type
     *
     * @return Collection<int, Page>
     */
    private function allMemberPages(string $type, BillKind $billKind): Collection
    {
        $members = Member::where('bill_kind', $billKind)
            ->with([
                'payments' => fn ($query) => $type::paymentsQuery($query)
                    ->orderByRaw('nr, member_id'),
            ])
            ->get()
            ->filter(fn (Member $member) => $member->payments->count() > 0);

        return $this->toPages($members);
    }

    /**
     * @param class-string<Letter>  $type
     * @param Collection<int, Page> $pages
     */
    private function resolve(string $type, Collection $pages): Letter
    {
        return new $type($pages);
    }

    /**
     * @param EloquentCollection<Member> $members
     *
     * @return Collection<int, Page>
     */
    private function toPages(EloquentCollection $members): Collection
    {
        return $members->groupBy(
            fn ($member) => Str::slug(
                "{$member->lastname}{$member->address}{$member->zip}{$member->location}",
            ),
        )->map(fn ($page) => new Page($page));
    }
}