<?php

declare(strict_types=1);

namespace App\Services;

use App\Models\User;
use Illuminate\Database\Eloquent\Builder;

/**
 * Resuelve qué collectors puede ver un usuario dado su rol y asignaciones.
 *
 * Jerarquía:
 *   admin                          → todos los collectors de la empresa ('all')
 *   supervisor + sees_all=true     → todos los collectors de la empresa ('all')
 *   supervisor + collectors asign. → solo sus collectors asignados ('assigned')
 *   supervisor sin asignaciones    → ninguno, señal 'unassigned' para la UI
 *
 * Esta clase es la única fuente de verdad para visibilidad de collectors.
 * TeamController, DashboardController, DelinquencyController, etc. deben
 * delegar aquí en lugar de implementar la lógica inline.
 *
 * Es instanciable directamente (sin inyección de contenedor) para tests unitarios:
 *   $resolver = new CollectorVisibilityResolver();
 *   $result   = $resolver->resolve($user);
 */
class CollectorVisibilityResolver
{
    // Valores posibles del campo 'mode' en el array de retorno.
    public const MODE_ALL = 'all';

    public const MODE_ASSIGNED = 'assigned';

    public const MODE_UNASSIGNED = 'unassigned';

    /**
     * Determina el modo de visibilidad y los IDs de collectors visibles.
     *
     * @return array{mode: string, collector_ids: int[]}
     *                                                   - mode='all':        el usuario ve todos los collectors de la empresa.
     *                                                   collector_ids está vacío (no se necesita filtro de IDs).
     *                                                   - mode='assigned':   el usuario ve solo los collectors en collector_ids.
     *                                                   - mode='unassigned': el usuario no tiene collectors asignados.
     *                                                   La UI debe mostrar el mensaje de "sin asignación".
     */
    public function resolve(User $user): array
    {
        // Admin siempre ve todo — independientemente de sees_all_collectors o asignaciones.
        if ($user->hasRole('admin')) {
            return ['mode' => self::MODE_ALL, 'collector_ids' => []];
        }

        // Supervisor con flag explícito → visión completa de la empresa.
        if ($user->sees_all_collectors) {
            return ['mode' => self::MODE_ALL, 'collector_ids' => []];
        }

        // Supervisor: ver solo sus collectors asignados.
        // Belt-and-suspenders: filtramos por company_id del supervisor para
        // evitar que un dato corrupto en la pivot exponga collectors de otra empresa.
        $ids = $user->assignedCollectors()
            ->where('users.company_id', $user->company_id)
            ->pluck('users.id')
            ->all();

        if (empty($ids)) {
            return ['mode' => self::MODE_UNASSIGNED, 'collector_ids' => []];
        }

        return ['mode' => self::MODE_ASSIGNED, 'collector_ids' => $ids];
    }

    /**
     * Aplica el filtro de visibilidad directamente sobre un Builder de User.
     *
     * Retorna null si el modo es 'unassigned' — el caller debe manejar ese
     * caso devolviendo una respuesta vacía con la flag en el JSON.
     *
     * Ejemplo de uso en un controller:
     *
     *   $query = User::role('collector')->where('users.company_id', $user->company_id);
     *   $query = $this->resolver->applyToQuery($query, $user);
     *   if ($query === null) {
     *       return response()->json(['unassigned' => true, 'data' => []]);
     *   }
     *   $collectors = $query->get();
     */
    public function applyToQuery(Builder $query, User $user): ?Builder
    {
        $visibility = $this->resolve($user);

        return match ($visibility['mode']) {
            self::MODE_ALL => $query,
            self::MODE_ASSIGNED => $query->whereIn('users.id', $visibility['collector_ids']),
            self::MODE_UNASSIGNED => null,
        };
    }

    /**
     * IDs de collectors visibles para filtrar una query por `collector_user_id`.
     *
     *   null  → sin filtro (ve a todos: admin / supervisor sees_all_collectors)
     *   []    → no ve a ninguno (supervisor sin asignaciones) → la query no debe traer nada
     *   int[] → solo esos collectors (supervisor con equipo asignado)
     *
     * Mapea directamente a la convención `?array $collectorIds` de los servicios
     * de métricas y a `whereIn('collector_user_id', $ids)` (donde `[]` ⇒ 0 filas).
     *
     * @return int[]|null
     */
    public function visibleCollectorIds(User $user): ?array
    {
        $visibility = $this->resolve($user);

        return match ($visibility['mode']) {
            self::MODE_ALL => null,
            self::MODE_ASSIGNED => $visibility['collector_ids'],
            self::MODE_UNASSIGNED => [],
            default => [], // fail-closed: un modo inesperado nunca debe abrir la visibilidad
        };
    }
}
