<?php

namespace Modules\MedOrganization\Services;

use Modules\MedOrganization\Entities\OrgFacilityProfile;

/**
 * Facility nesting rules (spec §5 hierarchy, decision D5): parent chains live
 * in org_facility_profiles.parent_location_id; max depth 5 and cycle
 * prevention are enforced HERE (service), not in the DB.
 */
class FacilityHierarchyService
{
    /**
     * Validate a prospective parent for a location. Returns null when valid,
     * otherwise a translatable error key (medorganization::lang.*).
     */
    public function validateParent(int $businessId, int $locationId, ?int $parentLocationId): ?string
    {
        if (empty($parentLocationId)) {
            return null; // root facility
        }

        if ($parentLocationId === $locationId) {
            return 'medorganization::lang.parent_cannot_be_self';
        }

        $profiles = $this->profileMap($businessId);

        if (! \App\BusinessLocation::where('business_id', $businessId)->where('id', $parentLocationId)->exists()) {
            return 'medorganization::lang.parent_not_in_business';
        }

        // Walk up from the prospective parent: detect cycles through this
        // location and measure the ancestor chain length.
        $depth = 1; // the location itself sits one level below its parent
        $cursor = $parentLocationId;
        $seen = [];
        while ($cursor !== null) {
            if ($cursor === $locationId || isset($seen[$cursor])) {
                return 'medorganization::lang.parent_creates_cycle';
            }
            $seen[$cursor] = true;
            $depth++;
            $cursor = $profiles[$cursor] ?? null;
        }

        // $depth now counts the location + all ancestors (root inclusive).
        if ($depth + $this->maxDescendantDepth($businessId, $locationId) > $this->maxDepth()) {
            return 'medorganization::lang.max_depth_exceeded';
        }

        return null;
    }

    /** Depth of a location within its facility tree (root = 1). */
    public function depthOf(int $businessId, int $locationId): int
    {
        $profiles = $this->profileMap($businessId);
        $depth = 1;
        $cursor = $profiles[$locationId] ?? null;
        $seen = [$locationId => true];
        while ($cursor !== null && ! isset($seen[$cursor]) && $depth <= $this->maxDepth()) {
            $seen[$cursor] = true;
            $depth++;
            $cursor = $profiles[$cursor] ?? null;
        }

        return $depth;
    }

    /** Longest chain of descendants hanging below the location (0 = leaf). */
    protected function maxDescendantDepth(int $businessId, int $locationId): int
    {
        $children = [];
        foreach ($this->profileMap($businessId) as $child => $parent) {
            if ($parent !== null) {
                $children[$parent][] = $child;
            }
        }

        $walk = function ($id, $guard) use (&$walk, $children) {
            if ($guard > $this->maxDepth() || empty($children[$id])) {
                return 0;
            }
            $max = 0;
            foreach ($children[$id] as $child) {
                $max = max($max, 1 + $walk($child, $guard + 1));
            }

            return $max;
        };

        return $walk($locationId, 0);
    }

    /** @return array<int, int|null> location_id => parent_location_id */
    protected function profileMap(int $businessId): array
    {
        return OrgFacilityProfile::forBusiness($businessId)
            ->pluck('parent_location_id', 'business_location_id')
            ->map(fn ($v) => $v !== null ? (int) $v : null)
            ->all();
    }

    protected function maxDepth(): int
    {
        return (int) config('medorganization.max_facility_depth', 5);
    }
}
