<?php

namespace App\Console\Commands\Concerns;

use App\Domain\Identity\Models\Branch;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;

/**
 * Who a scheduled command is allowed to tell about a branch's numbers.
 *
 * Branch enforcement is off in console on purpose — see docs/agent-rules/19 —
 * because a cron that silently billed or notified one branch would be far worse
 * than an unfiltered read. The cost of that decision is that a command which
 * *sends* something has to decide for itself who may see what, and both senders
 * here used to answer "every admin in the academy", which put every branch's
 * collections and stock in every branch manager's inbox.
 *
 * `role_user.branch_id` is the entitlement, and its NULL is meaningful:
 *
 *   branch_id = X  -> the role was granted at branch X; that branch's figures
 *   branch_id NULL -> the role was granted academy-wide; the consolidated view
 *
 * The column has existed since the first roles migration; it was simply never
 * read on this path. This lives in one place so both commands answer the
 * question identically and neither drifts.
 *
 * Raw query builder on purpose: `users`, `roles` and `role_user` are not
 * branch-scoped models, and every joined table is filtered by academy here
 * rather than relying on a scope that does not apply to DB::table().
 */
trait ResolvesBranchAdmins
{
    /**
     * Active, non-deleted users of the academy holding a role at or above
     * $minLevel, each carrying the branches that assignment entitles them to.
     *
     * @return Collection<int, object> {id, email, academyWide, branchIds}
     */
    protected function branchAdmins(int $academyId, int $minLevel): Collection
    {
        return DB::table('role_user')
            ->join('users', 'users.id', '=', 'role_user.user_id')
            ->join('roles', 'roles.id', '=', 'role_user.role_id')
            ->where('users.academy_id', $academyId)
            ->where('users.status', 'active')
            // DB::table() sees through SoftDeletes, so a removed admin kept
            // receiving the academy's takings by email until this line.
            ->whereNull('users.deleted_at')
            ->where('roles.academy_id', $academyId)
            ->where('roles.level', '>=', $minLevel)
            ->select('users.id', 'users.email', 'role_user.branch_id')
            ->get()
            ->groupBy('id')
            ->map(fn (Collection $rows) => (object) [
                'id' => (int) $rows->first()->id,
                'email' => $rows->first()->email,
                // One academy-wide assignment is enough: it is the broader grant.
                'academyWide' => $rows->contains(fn ($row) => $row->branch_id === null),
                'branchIds' => $rows->pluck('branch_id')
                    ->filter()
                    ->map(fn ($id) => (int) $id)
                    ->unique()
                    ->values()
                    ->all(),
            ])
            ->values();
    }

    /**
     * Branch names for labelling a message, keyed by id.
     *
     * Inactive branches are included: a message about a branch that was closed
     * yesterday still has to name it.
     *
     * @return array<int, string>
     */
    protected function branchNames(int $academyId): array
    {
        $names = [];

        foreach (Branch::where('academy_id', $academyId)->get(['id', 'name', 'name_ar']) as $branch) {
            $names[(int) $branch->id] = $branch->name_ar ?: ($branch->name ?? "#{$branch->id}");
        }

        return $names;
    }

    /**
     * @param  array<int, int>  $branchIds
     * @param  array<int, string>  $names
     */
    protected function branchLabel(array $branchIds, array $names): string
    {
        return implode('، ', array_map(fn ($id) => $names[(int) $id] ?? "#{$id}", $branchIds));
    }
}
