<?php
/**
 * Vertical model — DATA-DRIVEN (no hardcoded vertical list).
 *
 * Verticals (Coding, AR, Billing, … and any future one) live in the `verticals` table. A user "belongs to"
 * a vertical through their ROLE: a vertical's permission group_name equals the vertical's lowercased code
 * (vertical CODING ↔ permission group 'coding', BILLING ↔ 'billing', AR ↔ 'ar'). So a new vertical needs only
 * its rows + permission group — no code change here. NOTHING in app logic may hardcode a vertical code.
 *
 * Master data (client » process » facility » provider) is SHARED across all verticals; separation is by the
 * USER (role → vertical) + the WORK (each vertical's own work tables) + the user's alignment, NOT by the data.
 */

/** All active verticals as [id => ['id','code','name']], request-cached. */
function app_verticals(): array
{
    static $cache = null;
    if ($cache !== null) return $cache;
    $cache = [];
    try {
        foreach (db()->query("SELECT id, code, name FROM verticals WHERE deleted_at IS NULL AND status='active' ORDER BY name") as $r) {
            $cache[(int) $r['id']] = ['id' => (int) $r['id'], 'code' => $r['code'], 'name' => $r['name']];
        }
    } catch (Throwable $e) { $cache = []; }
    return $cache;
}

/** The permission group_name that maps to a vertical code (the one data-driven convention). */
function app_vertical_group(string $code): string { return strtolower(trim($code)); }

/**
 * Canonical role/group token for a vertical ROW — robust to the prod realities the raw code is NOT:
 * dedup-suffixed codes ('CODING-2' when the original 'CODING' vertical was soft-deleted) and empty codes.
 * Prefers the stable NAME, strips a trailing dedup number, keeps letters only. → 'coding' | 'ar' | 'billing'.
 * Use this (not app_vertical_group($v['code'])) anywhere a vertical must resolve to a role prefix / group.
 */
function app_vertical_group_canonical(?array $v): string
{
    if (!$v) return '';
    foreach ([$v['name'] ?? '', $v['code'] ?? ''] as $raw) {
        $g = strtolower(trim((string) $raw));
        $g = preg_replace('/[\s_-]*\d+$/', '', $g);   // drop a trailing dedup suffix: 'coding-2' / 'coding 2' → 'coding'
        $g = preg_replace('/[^a-z]/', '', $g);        // letters only
        if ($g !== '') return $g;
    }
    return '';
}

/** One vertical row by id (or null). */
function app_vertical(int $id): ?array { return app_verticals()[$id] ?? null; }

/** Vertical id for a code (case-insensitive), or null. */
function app_vertical_id_by_code(string $code): ?int
{
    $code = strtolower(trim($code));
    foreach (app_verticals() as $vid => $v) if (strtolower($v['code']) === $code) return $vid;
    return null;
}

/**
 * The vertical ids a user WORKS, derived from their ROLES (a role's permissions' group_name = a vertical code).
 * Sysadmin → all verticals. Used by the alignment assignment flow and the management pages' vertical context.
 */
function app_user_vertical_ids(int $uid): array
{
    if ($uid <= 0) return [];
    if (user_has_role($uid, 'sysadmin')) return array_keys(app_verticals());
    try {
        // Derive the vertical group from the permission's group_name, falling back to the permission CODE prefix
        // ('coding.work' → 'coding') so it still resolves when group_name was never populated (old-build prod).
        $st = db()->prepare(
            "SELECT DISTINCT LOWER(COALESCE(NULLIF(p.group_name, ''), SUBSTRING_INDEX(p.code, '.', 1))) g
             FROM user_roles ur
             JOIN role_permissions rp ON rp.role_id = ur.role_id
             JOIN permissions p ON p.id = rp.permission_id
             WHERE ur.user_id = ?"
        );
        $st->execute([$uid]);
        $groups = $st->fetchAll(PDO::FETCH_COLUMN);
    } catch (Throwable $e) { return []; }
    $out = [];
    foreach (app_verticals() as $vid => $v) {
        if (in_array(app_vertical_group($v['code']), $groups, true)) $out[] = $vid;
    }
    // ALSO honor the explicit employee Vertical set on the users row (users.vertical = 'Coding'/'AR'/'Billing', matched
    // by vertical name OR code). This is the authoritative admin-set signal and makes alignment work even when a role's
    // permissions don't resolve to a vertical group (old-build prod, or a role with no permissions yet).
    try {
        $uvq = db()->prepare("SELECT vertical FROM users WHERE id=?"); $uvq->execute([$uid]);
        $uv  = strtolower(trim((string) $uvq->fetchColumn()));
        if ($uv !== '') foreach (app_verticals() as $vid => $v) {
            if (($uv === strtolower((string) $v['code']) || $uv === strtolower((string) $v['name'])) && !in_array($vid, $out, true)) $out[] = $vid;
        }
    } catch (Throwable $e) { /* users.vertical may be absent on a very old schema — ignore */ }
    return $out;
}

/** Does this user work in the given vertical id? (sysadmin = yes for all.) */
function app_user_in_vertical(int $uid, int $verticalId): bool
{
    return in_array($verticalId, app_user_vertical_ids($uid), true);
}

/**
 * The SHARED master data (clients/processes/facilities/providers + Hierarchy) may be managed by ANY vertical's
 * supervisor or reference-admin (or sysadmin). Data-driven — any '*.supervise' or '*.reference' permission
 * qualifies, so a new vertical's roles gain master-data management with no code change here.
 */
function can_manage_master_data(): bool
{
    // DISCRETE, per-user permission (migration master_data_manage_perm_v1 seeds it to every supervisor/manager role,
    // so existing access is preserved; admins can grant/deny it independently on the Permissions page).
    return is_sysadmin() || in_array('master.data.manage', current_user_permissions(), true);
}
