<?php

declare(strict_types=1);
require DENTAL_ROOT . '/app/bootstrap.php';
require_login();
require_roles(['super_admin','clinic_admin']);
require DENTAL_ROOT . '/app/layout.php';
require DENTAL_ROOT . '/app/module_helpers.php';

$pdo=db();$error='';$isSuper=superuser_primary_workspace();$currentUserId=(int)(current_user()['id']??0);
$inviteReady=staff_invite_schema_ready()&&email_template_schema_ready();
$roleAssignmentsReady=user_role_assignments_schema_ready();
$structuredNameReady=schema_column_exists(table('users'),'first_name')&&schema_column_exists(table('users'),'middle_name')&&schema_column_exists(table('users'),'last_name');
$employeeNoReady=schema_column_exists(table('users'),'employee_no');$jobTitleReady=schema_column_exists(table('users'),'job_title');
$branches=$pdo->query('SELECT id,name FROM `' . table('branches') . '` WHERE is_active=1 ORDER BY name')->fetchAll();
$roleCodes=$isSuper?'"clinic_admin","receptionist","staff","dentist","dental_assistant","cashier","inventory_officer"':'"receptionist","staff","dentist","dental_assistant","cashier","inventory_officer"';
$roles=$pdo->query('SELECT id,code,name FROM `' . table('roles') . '` WHERE code IN ('.$roleCodes.') ORDER BY name')->fetchAll();
$allowedRoleIds=array_map(static fn(array $row):int=>(int)$row['id'],$roles);

function staff_target(PDO $pdo,int $id): array
{
    $stmt=$pdo->prepare('SELECT u.*,r.code role_code,r.name role_name FROM `' . table('users') . '` u JOIN `' . table('roles') . '` r ON r.id=u.role_id WHERE u.id=? LIMIT 1');$stmt->execute([$id]);$row=$stmt->fetch();if(!$row)throw new RuntimeException('Staff account not found.');return $row;
}
function staff_assert_manageable(array $target,bool $isSuper,int $currentUserId): void
{
    if(($target['role_code']??'')==='super_admin')throw new RuntimeException('Superuser accounts are managed only through protected system controls.');
    if(!$isSuper&&($target['role_code']??'')==='clinic_admin')throw new RuntimeException('Only the Superuser may manage Administrator accounts.');
}

function staff_reference_blockers(PDO $pdo,int $userId): array
{
    $schema=(string)$pdo->query('SELECT DATABASE()')->fetchColumn();
    if($schema==='')return ['Database schema could not be identified.'];
    $stmt=$pdo->prepare('SELECT k.TABLE_NAME,k.COLUMN_NAME,rc.DELETE_RULE FROM information_schema.KEY_COLUMN_USAGE k JOIN information_schema.REFERENTIAL_CONSTRAINTS rc ON rc.CONSTRAINT_SCHEMA=k.CONSTRAINT_SCHEMA AND rc.CONSTRAINT_NAME=k.CONSTRAINT_NAME AND rc.TABLE_NAME=k.TABLE_NAME WHERE k.REFERENCED_TABLE_SCHEMA=? AND k.REFERENCED_TABLE_NAME=? ORDER BY k.TABLE_NAME,k.COLUMN_NAME');
    $stmt->execute([$schema,table('users')]);
    $blockers=[];
    foreach($stmt->fetchAll() as $ref){
        if(strtoupper((string)($ref['DELETE_RULE']??''))==='CASCADE')continue;
        $tableName=(string)$ref['TABLE_NAME'];$columnName=(string)$ref['COLUMN_NAME'];
        $quotedTable='`'.str_replace('`','``',$tableName).'`';$quotedColumn='`'.str_replace('`','``',$columnName).'`';
        $countStmt=$pdo->prepare('SELECT COUNT(*) FROM '.$quotedTable.' WHERE '.$quotedColumn.'=?');$countStmt->execute([$userId]);$count=(int)$countStmt->fetchColumn();
        if($count>0)$blockers[]=$tableName.'.'.$columnName.' ('.$count.')';
    }
    return $blockers;
}
function staff_cancel_open_invites(PDO $pdo,int $userId): void
{
    if(!staff_invite_schema_ready())return;
    $stmt=$pdo->prepare('SELECT email_outbox_id FROM `' . table('staff_account_invites') . '` WHERE user_id=? AND used_at IS NULL');$stmt->execute([$userId]);
    $outboxIds=array_values(array_filter(array_map('intval',$stmt->fetchAll(PDO::FETCH_COLUMN))));
    if($outboxIds&&schema_table_exists(table('email_outbox'))){
        $marks=implode(',',array_fill(0,count($outboxIds),'?'));
        $pdo->prepare('UPDATE `' . table('email_outbox') . '` SET status="cancelled",updated_at=NOW() WHERE id IN ('.$marks.') AND status IN ("pending","processing","retry")')->execute($outboxIds);
    }
    $pdo->prepare('DELETE FROM `' . table('staff_account_invites') . '` WHERE user_id=? AND used_at IS NULL')->execute([$userId]);
}
function staff_delivery_flash(array $delivery,string $successLead): void
{
    $status=(string)($delivery['status']??'pending');
    $driver=(string)(app_config('mail.driver','smtp'));
    if($status==='sent'&&$driver==='log'){
        flash('warning',$successLead.' The email driver is Log only, so the setup message was written to the server log and was not delivered to an inbox. Configure SMTP, then resend the setup link.');
    }elseif($status==='sent'){
        flash('success',$successLead.' The mail server accepted the setup email. Ask the user to check the inbox and spam folder.');
    }elseif(in_array($status,['pending','retry'],true)){
        $suffix=!empty($delivery['error'])?' Last error: '.(string)$delivery['error']:'';
        flash('warning',$successLead.' The setup email is '.strtolower(email_delivery_label($status)).'. Review Email & SMTP and process the queue.'.$suffix);
    }else{
        flash('warning',$successLead.' The account exists, but email delivery failed. Open Email & SMTP, correct the configuration, then use Resend setup link.');
    }
}

function staff_role_assignments(PDO $pdo,int $userId,array $fallback): array
{
    if($userId<=0)return [];
    if(user_role_assignments_schema_ready()){
        $hasExpiry=schema_column_exists(table('user_role_assignments'),'expires_at');
        $hasLastSwitch=schema_column_exists(table('user_role_assignments'),'last_switched_at');
        $select='r.id,r.code,r.name,ura.assignment_type,ura.status,ura.note,assigner.name assigned_by_name,ura.created_at assigned_at'
            .($hasExpiry?',ura.expires_at':',NULL expires_at')
            .($hasLastSwitch?',ura.last_switched_at':',NULL last_switched_at');
        $stmt=$pdo->prepare('SELECT '.$select.' FROM `' . table('user_role_assignments') . '` ura JOIN `' . table('roles') . '` r ON r.id=ura.role_id LEFT JOIN `' . table('users') . '` assigner ON assigner.id=ura.assigned_by WHERE ura.user_id=? AND ura.status="active" ORDER BY FIELD(ura.assignment_type,"primary","secondary"),r.name');
        $stmt->execute([$userId]);$rows=$stmt->fetchAll();if($rows)return $rows;
    }
    return [['id'=>(int)($fallback['role_id']??0),'code'=>(string)($fallback['role_code']??''),'name'=>(string)($fallback['role_name']??'Role'),'assignment_type'=>'primary','status'=>'active','note'=>null,'expires_at'=>null,'last_switched_at'=>null,'assigned_by_name'=>null,'assigned_at'=>null]];
}
function staff_role_codes_for_ids(PDO $pdo,array $roleIds): array
{
    $roleIds=array_values(array_unique(array_filter(array_map('intval',$roleIds),static fn(int $id):bool=>$id>0)));
    if(!$roleIds)return [];
    $marks=implode(',',array_fill(0,count($roleIds),'?'));
    $stmt=$pdo->prepare('SELECT code FROM `' . table('roles') . '` WHERE id IN ('.$marks.')');
    $stmt->execute($roleIds);
    return array_values(array_unique(array_filter(array_map('strval',$stmt->fetchAll(PDO::FETCH_COLUMN)))));
}
function staff_validate_role_separation(PDO $pdo,int $primaryRoleId,array $secondaryRoleIds): array
{
    $codes=staff_role_codes_for_ids($pdo,array_merge([$primaryRoleId],$secondaryRoleIds));
    $primaryCodes=staff_role_codes_for_ids($pdo,[$primaryRoleId]);
    $primaryCode=(string)($primaryCodes[0]??'');
    if(in_array('clinic_admin',$codes,true)&&$primaryCode!=='clinic_admin'){
        throw new RuntimeException('Administrator authority cannot be added as a secondary role. Create or promote a dedicated Administrator account, then assign compatible operational roles to that Administrator when required.');
    }
    $exclusive=array_values(array_intersect($codes,['super_admin','staff']));
    if($exclusive && count($codes)>1){
        $label=match($exclusive[0]){'super_admin'=>'Superuser','clinic_admin'=>'Clinic Administrator','cashier'=>'Cashier','staff'=>'General Staff',default=>'High-risk role'};
        throw new RuntimeException($label.' must use a dedicated named account and cannot be combined with another role. Use the Active role switcher only for compatible assigned roles.');
    }
    $conflicts=[
        ['dentist','dental_assistant','Dentist and Dental Assistant'],
        ['dentist','inventory_officer','Dentist and Inventory Officer'],
        ['dental_assistant','inventory_officer','Dental Assistant and Inventory Officer'],
        ['clinic_admin','cashier','Clinic Administrator and Cashier'],
    ];
    foreach($conflicts as [$left,$right,$label]){
        if(in_array($left,$codes,true)&&in_array($right,$codes,true)){
            throw new RuntimeException($label.' cannot be assigned to the same account. Use separate named accounts to preserve independent clinical verification and stock custody.');
        }
    }
    return $codes;
}

function staff_sync_role_assignments(PDO $pdo,int $userId,int $primaryRoleId,array $secondaryRoleIds,int $actorId,array $roleMetadata=[]): array
{
    $secondaryRoleIds=array_values(array_unique(array_filter(array_map('intval',$secondaryRoleIds),static fn(int $id):bool=>$id>0&&$id!==$primaryRoleId)));
    $allRoleIds=array_values(array_unique(array_merge([$primaryRoleId],$secondaryRoleIds)));
    $userSets=['role_id=?','updated_at=NOW()'];$userParams=[$primaryRoleId];
    if(schema_column_exists(table('users'),'role_context_version'))$userSets[]='role_context_version=role_context_version+1';
    $userParams[]=$userId;
    $pdo->prepare('UPDATE `' . table('users') . '` SET '.implode(',',$userSets).' WHERE id=?')->execute($userParams);
    if(user_role_assignments_schema_ready()){
        $hasExpiry=schema_column_exists(table('user_role_assignments'),'expires_at');
        $pdo->prepare('UPDATE `' . table('user_role_assignments') . '` SET status="inactive",updated_at=NOW() WHERE user_id=?')->execute([$userId]);
        if($hasExpiry){
            $upsert=$pdo->prepare('INSERT INTO `' . table('user_role_assignments') . '` (user_id,role_id,assigned_by,assignment_type,status,note,expires_at,created_at,updated_at) VALUES (?,?,?,?,"active",?,?,NOW(),NOW()) ON DUPLICATE KEY UPDATE assigned_by=VALUES(assigned_by),assignment_type=VALUES(assignment_type),status="active",note=VALUES(note),expires_at=VALUES(expires_at),updated_at=NOW()');
        }else{
            $upsert=$pdo->prepare('INSERT INTO `' . table('user_role_assignments') . '` (user_id,role_id,assigned_by,assignment_type,status,note,created_at,updated_at) VALUES (?,?,?,?,"active",?,NOW(),NOW()) ON DUPLICATE KEY UPDATE assigned_by=VALUES(assigned_by),assignment_type=VALUES(assignment_type),status="active",note=VALUES(note),updated_at=NOW()');
        }
        foreach($allRoleIds as $roleId){
            $isPrimary=$roleId===$primaryRoleId;
            $meta=(array)($roleMetadata[$roleId]??[]);
            $note=$isPrimary?'Primary role':(trim((string)($meta['note']??''))?:null);
            $expires=$isPrimary?null:($meta['expires_at']??null);
            $params=[$userId,$roleId,$actorId?:null,$isPrimary?'primary':'secondary',$note];
            if($hasExpiry)$params[]=$expires;
            $upsert->execute($params);
        }
    }
    if(!$allRoleIds)return [];
    $marks=implode(',',array_fill(0,count($allRoleIds),'?'));
    $stmt=$pdo->prepare('SELECT id,code,name FROM `' . table('roles') . '` WHERE id IN ('.$marks.') ORDER BY FIELD(id,'.$marks.')');
    $stmt->execute(array_merge($allRoleIds,$allRoleIds));
    return $stmt->fetchAll();
}

function staff_role_assignment_metadata(array $roleIds): array
{
    $metadata=[];
    $notes=(array)($_POST['role_notes']??[]);
    $expires=(array)($_POST['role_expires_at']??[]);
    foreach($roleIds as $roleId){
        $roleId=(int)$roleId;if($roleId<1)continue;
        $note=mb_substr(trim((string)($notes[$roleId]??'')),0,255);
        if($note==='')throw new RuntimeException('Enter an assignment reason for every additional role.');
        $expiryText=trim((string)($expires[$roleId]??''));$expiry=null;
        if($expiryText!==''){
            $date=DateTimeImmutable::createFromFormat('!Y-m-d',$expiryText);
            if(!$date||$date->format('Y-m-d')!==$expiryText)throw new RuntimeException('Choose a valid role expiration date.');
            $date=$date->setTime(23,59,59);
            if($date<=new DateTimeImmutable('now'))throw new RuntimeException('Role expiration must be in the future.');
            $expiry=$date->format('Y-m-d H:i:s');
        }
        $metadata[$roleId]=['note'=>$note?:null,'expires_at'=>$expiry];
    }
    return $metadata;
}
function staff_ensure_dentist_profile(PDO $pdo,int $userId,string $displayName,?int $branchId): void
{
    if(!doctor_workflow_schema_ready()||!schema_table_exists(table('dentist_profiles')))return;
    if(schema_column_exists(table('dentist_profiles'),'credential_status')){
        $pdo->prepare('INSERT INTO `' . table('dentist_profiles') . '` (user_id,display_name,primary_branch_id,profile_status,credential_status,created_at) VALUES (?,?,?,"draft","pending",NOW()) ON DUPLICATE KEY UPDATE display_name=VALUES(display_name),primary_branch_id=COALESCE(VALUES(primary_branch_id),primary_branch_id),updated_at=NOW()')->execute([$userId,$displayName,$branchId]);
    }else{
        $pdo->prepare('INSERT INTO `' . table('dentist_profiles') . '` (user_id,display_name,primary_branch_id,profile_status,created_at) VALUES (?,?,?,"draft",NOW()) ON DUPLICATE KEY UPDATE display_name=VALUES(display_name),primary_branch_id=COALESCE(VALUES(primary_branch_id),primary_branch_id),updated_at=NOW()')->execute([$userId,$displayName,$branchId]);
    }
}

if($_SERVER['REQUEST_METHOD']==='POST'){
    try{
        verify_csrf();$action=(string)($_POST['action']??'create');
        if($action==='create'){
            if(!$inviteReady)throw new RuntimeException('Apply Version 0.5.8.3 before creating invitation-based staff accounts.');
            $firstName=trim((string)($_POST['first_name']??''));$middleName=trim((string)($_POST['middle_name']??''));$lastName=trim((string)($_POST['last_name']??''));$name=person_name_from_parts($firstName,$middleName,$lastName);$email=strtolower(trim((string)($_POST['email']??'')));$roleId=(int)($_POST['role_id']??0);$branchId=(int)($_POST['branch_id']??0);
            if(!$structuredNameReady)throw new RuntimeException('Apply Version 0.5.9.8.32 before creating structured staff profiles.');
            if($firstName===''||$lastName===''||!filter_var($email,FILTER_VALIDATE_EMAIL)||!in_array($roleId,$allowedRoleIds,true))throw new RuntimeException('Complete the first name, last name, valid email, and authorized role.');
            $exists=$pdo->prepare('SELECT id FROM `' . table('users') . '` WHERE LOWER(email)=? LIMIT 1');$exists->execute([$email]);if($exists->fetchColumn())throw new RuntimeException('A staff account already uses this email address. Open that account and resend its setup link.');
            $pdo->beginTransaction();
            $placeholder=password_hash(bin2hex(random_bytes(32)),PASSWORD_DEFAULT);
            $stmt=$pdo->prepare('INSERT INTO `' . table('users') . '` (branch_id,role_id,name,first_name,middle_name,last_name,email,password_hash,status,email_verified_at,must_change_password,created_at) VALUES (?,?,?,?,?,?,?,?,"active",NULL,1,NOW())');$stmt->execute([$branchId?:null,$roleId,$name,$firstName,$middleName?:null,$lastName,$email,$placeholder]);$id=(int)$pdo->lastInsertId();
            $roleStmt=$pdo->prepare('SELECT code,name FROM `' . table('roles') . '` WHERE id=?');$roleStmt->execute([$roleId]);$roleRow=$roleStmt->fetch();$createdRole=(string)($roleRow['code']??'staff');
            if($roleAssignmentsReady)staff_sync_role_assignments($pdo,$id,$roleId,[],$currentUserId);
            if($createdRole==='dentist'&&doctor_workflow_schema_ready()){
                if(schema_column_exists(table('dentist_profiles'),'credential_status')){
                    $pdo->prepare('INSERT INTO `' . table('dentist_profiles') . '` (user_id,display_name,primary_branch_id,profile_status,credential_status,created_at) VALUES (?,?,?,"draft","pending",NOW()) ON DUPLICATE KEY UPDATE display_name=VALUES(display_name),primary_branch_id=VALUES(primary_branch_id),profile_status="draft",credential_status="pending",credential_verified_by=NULL,credential_verified_at=NULL')->execute([$id,$name,$branchId?:null]);
                }else{
                    $pdo->prepare('INSERT INTO `' . table('dentist_profiles') . '` (user_id,display_name,primary_branch_id,profile_status,created_at) VALUES (?,?,?,"draft",NOW()) ON DUPLICATE KEY UPDATE display_name=VALUES(display_name),primary_branch_id=VALUES(primary_branch_id),profile_status="draft"')->execute([$id,$name,$branchId?:null]);
                }
                audit_required('dentist_created_pending_credentials','dentist_profile',$id,['auto_privileges_granted'=>false]);
            }
            audit_required('staff_user_created','user',$id,['email'=>$email,'role'=>$createdRole]);$pdo->commit();
            try{$delivery=create_staff_account_invite($pdo,$id,$email,'staff_account_invite',72);staff_delivery_flash($delivery,'Staff account created.');}
            catch(Throwable $mailError){flash('warning','Staff account created, but the setup email could not be queued: '.$mailError->getMessage().' Use Resend setup link after correcting Email & SMTP.');}
        }elseif($action==='resend_invite'){
            if(!$inviteReady)throw new RuntimeException('Apply Version 0.5.8.3 before sending secure setup links.');
            $id=(int)($_POST['user_id']??0);$target=staff_target($pdo,$id);staff_assert_manageable($target,$isSuper,$currentUserId);
            $templateKey=empty($target['email_verified_at'])?'staff_account_invite':'staff_password_setup';
            $delivery=create_staff_account_invite($pdo,$id,(string)$target['email'],$templateKey,72);
            audit('staff_account_invite_resent','user',$id,['email'=>$target['email'],'status'=>$delivery['status']??'pending']);
            staff_delivery_flash($delivery,'A new secure setup link was created.');
        }elseif($action==='update_name'){
            if(!$structuredNameReady)throw new RuntimeException('Apply Version 0.5.9.8.32 before editing structured staff names.');
            $id=(int)($_POST['user_id']??0);$target=staff_target($pdo,$id);staff_assert_manageable($target,$isSuper,$currentUserId);
            if(!empty($target['email_verified_at'])||!empty($target['last_login_at']))throw new RuntimeException('Activated staff maintain personal identity in Profile Center. Administrators manage only work assignment, roles, branch, and account status.');
            $firstName=trim((string)($_POST['first_name']??''));$middleName=trim((string)($_POST['middle_name']??''));$lastName=trim((string)($_POST['last_name']??''));
            if($firstName===''||$lastName==='')throw new RuntimeException('First name and last name are required.');
            $name=person_name_from_parts($firstName,$middleName,$lastName);
            $pdo->beginTransaction();
            $pdo->prepare('UPDATE `' . table('users') . '` SET name=?,first_name=?,middle_name=?,last_name=?,updated_at=NOW() WHERE id=?')->execute([$name,$firstName,$middleName?:null,$lastName,$id]);
            if(($target['role_code']??'')==='dentist'&&schema_table_exists(table('dentist_profiles')))$pdo->prepare('UPDATE `' . table('dentist_profiles') . '` SET display_name=?,updated_at=NOW() WHERE user_id=?')->execute([$name,$id]);
            audit_required('staff_invitation_identity_corrected','user',$id,['first_name'=>$firstName,'middle_name'=>$middleName?:null,'last_name'=>$lastName],['name'=>$target['name']??'']);$pdo->commit();flash('success','Invitation identity corrected. The staff member will maintain personal details in Profile Center after activation.');
        }elseif($action==='update_employment'){
            $id=(int)($_POST['user_id']??0);$target=staff_target($pdo,$id);staff_assert_manageable($target,$isSuper,$currentUserId);
            if($id===$currentUserId)throw new RuntimeException('Ask another authorized administrator to change your own protected work assignment.');
            $branchId=max(0,(int)($_POST['branch_id']??0));$employeeNo=trim((string)($_POST['employee_no']??''));$jobTitle=trim((string)($_POST['job_title']??''));$employmentType=(string)($_POST['employment_type']??'');if(!in_array($employmentType,['','full_time','part_time','visiting','contractual'],true))throw new RuntimeException('Choose a valid Dentist employment type.');
            if($branchId>0){$branchCheck=$pdo->prepare('SELECT COUNT(*) FROM `' . table('branches') . '` WHERE id=? AND is_active=1');$branchCheck->execute([$branchId]);if((int)$branchCheck->fetchColumn()!==1)throw new RuntimeException('Choose an active clinic branch.');}
            if($employeeNo!==''&&$employeeNoReady){$duplicate=$pdo->prepare('SELECT id FROM `' . table('users') . '` WHERE employee_no=? AND id<>? LIMIT 1');$duplicate->execute([$employeeNo,$id]);if($duplicate->fetchColumn())throw new RuntimeException('Another staff account already uses this employee number.');}
            $sets=['branch_id=?'];$params=[$branchId?:null];
            if($employeeNoReady){$sets[]='employee_no=?';$params[]=$employeeNo!==''?$employeeNo:null;}
            if($jobTitleReady){$sets[]='job_title=?';$params[]=$jobTitle!==''?$jobTitle:null;}
            if(schema_column_exists(table('users'),'role_context_version'))$sets[]='role_context_version=role_context_version+1';
            $sets[]='updated_at=NOW()';$params[]=$id;
            $pdo->beginTransaction();
            $pdo->prepare('UPDATE `' . table('users') . '` SET '.implode(',',$sets).' WHERE id=?')->execute($params);
            if(schema_table_exists(table('dentist_profiles'))){
                $dentistSets=['primary_branch_id=?','display_name=?'];$dentistParams=[$branchId?:null,user_full_name($target)];
                if($employmentType!==''){$dentistSets[]='employment_type=?';$dentistParams[]=$employmentType;}
                $dentistSets[]='updated_at=NOW()';$dentistParams[]=$id;
                $pdo->prepare('UPDATE `' . table('dentist_profiles') . '` SET '.implode(',',$dentistSets).' WHERE user_id=?')->execute($dentistParams);
            }
            audit_required('staff_work_assignment_updated','user',$id,['branch_id'=>$branchId?:null,'employee_no'=>$employeeNo?:null,'job_title'=>$jobTitle?:null,'employment_type'=>$employmentType?:null],['branch_id'=>$target['branch_id']??null,'employee_no'=>$target['employee_no']??null,'job_title'=>$target['job_title']??null]);
            $pdo->commit();flash('success','Work assignment updated. Personal identity and contact details were not changed.');
        }elseif($action==='status'){
            $id=(int)($_POST['user_id']??0);$target=staff_target($pdo,$id);staff_assert_manageable($target,$isSuper,$currentUserId);$status=(string)($_POST['status']??'');if(!in_array($status,['active','inactive','locked'],true))throw new RuntimeException('Choose a valid account status.');if($id===$currentUserId&&$status!=='active')throw new RuntimeException('You cannot deactivate or lock your own signed-in account.');
            $pdo->beginTransaction();$pdo->prepare('UPDATE `' . table('users') . '` SET status=?,updated_at=NOW() WHERE id=?')->execute([$status,$id]);audit_required('staff_status_updated','user',$id,['status'=>$status],['status'=>$target['status']]);$pdo->commit();flash('success','Account status updated.');
        }elseif($action==='update_roles'){
            if(!$roleAssignmentsReady)throw new RuntimeException('Apply Version 0.5.9.8.74 before assigning dual roles.');
            $id=(int)($_POST['user_id']??0);$target=staff_target($pdo,$id);staff_assert_manageable($target,$isSuper,$currentUserId);if($id===$currentUserId)throw new RuntimeException('For safety, a signed-in user cannot change their own role assignments. Ask another authorized administrator to do it.');
            $primaryRoleId=(int)($_POST['primary_role_id']??0);$additionalRoleIds=array_values(array_unique(array_filter(array_map('intval',(array)($_POST['additional_role_ids']??[])))));
            if(!in_array($primaryRoleId,$allowedRoleIds,true))throw new RuntimeException('Choose an authorized primary role.');
            foreach($additionalRoleIds as $rid){if(!in_array($rid,$allowedRoleIds,true))throw new RuntimeException('One or more additional roles are not authorized for your account to assign.');}
            staff_validate_role_separation($pdo,$primaryRoleId,$additionalRoleIds);
            $roleMetadata=staff_role_assignment_metadata($additionalRoleIds);
            $pdo->beginTransaction();
            $assignedRoles=staff_sync_role_assignments($pdo,$id,$primaryRoleId,$additionalRoleIds,$currentUserId,$roleMetadata);
            $assignedCodes=array_map(static fn(array $row):string=>(string)$row['code'],$assignedRoles);
            if(in_array('dentist',$assignedCodes,true))staff_ensure_dentist_profile($pdo,$id,user_full_name($target),(int)($target['branch_id']??0)?:null);
            audit_required('staff_roles_updated','user',$id,['primary_role_id'=>$primaryRoleId,'role_codes'=>$assignedCodes,'secondary_role_controls'=>$roleMetadata],['role'=>$target['role_code']]);$pdo->commit();flash('success','Staff role assignments updated. The account will return to its primary role on the next request.');
        }elseif($action==='force_primary_role'){
            $id=(int)($_POST['user_id']??0);$target=staff_target($pdo,$id);staff_assert_manageable($target,$isSuper,$currentUserId);
            if(!schema_column_exists(table('users'),'role_context_version'))throw new RuntimeException('Apply Version 0.8.1.2 before forcing an active-role reset.');
            $pdo->beginTransaction();
            $pdo->prepare('UPDATE `' . table('users') . '` SET role_context_version=role_context_version+1,updated_at=NOW() WHERE id=?')->execute([$id]);
            audit_required('staff_active_role_reset','user',$id,['forced_primary_role'=>(string)$target['role_code']]);
            $pdo->commit();flash('success','The account will return to its primary role on the next request.');
        }elseif($action==='correct_email'){
            if(!$inviteReady)throw new RuntimeException('Apply Version 0.5.8.3 before correcting invitation email addresses.');
            $id=(int)($_POST['user_id']??0);$newEmail=strtolower(trim((string)($_POST['new_email']??'')));if(!filter_var($newEmail,FILTER_VALIDATE_EMAIL))throw new RuntimeException('Enter a valid replacement email address.');
            $pdo->beginTransaction();
            $stmt=$pdo->prepare('SELECT u.*,r.code role_code,r.name role_name FROM `' . table('users') . '` u JOIN `' . table('roles') . '` r ON r.id=u.role_id WHERE u.id=? FOR UPDATE');$stmt->execute([$id]);$target=$stmt->fetch();if(!$target)throw new RuntimeException('Staff account not found.');staff_assert_manageable($target,$isSuper,$currentUserId);
            if($id===$currentUserId)throw new RuntimeException('Use the protected Superuser account settings to change your own email.');
            if(!empty($target['email_verified_at'])||!empty($target['last_login_at']))throw new RuntimeException('This account has already been activated or used. Deactivate it and create a replacement account so historical records keep the original identity.');
            $exists=$pdo->prepare('SELECT id FROM `' . table('users') . '` WHERE LOWER(email)=? AND id<>? LIMIT 1');$exists->execute([$newEmail,$id]);if($exists->fetchColumn())throw new RuntimeException('Another account already uses that email address.');
            $oldEmail=(string)$target['email'];staff_cancel_open_invites($pdo,$id);
            $placeholder=password_hash(bin2hex(random_bytes(32)),PASSWORD_DEFAULT);
            $pdo->prepare('UPDATE `' . table('users') . '` SET email=?,password_hash=?,email_verified_at=NULL,must_change_password=1,updated_at=NOW() WHERE id=?')->execute([$newEmail,$placeholder,$id]);
            audit_required('staff_email_corrected','user',$id,['email'=>$newEmail],['email'=>$oldEmail]);$pdo->commit();
            try{$delivery=create_staff_account_invite($pdo,$id,$newEmail,'staff_account_invite',72);staff_delivery_flash($delivery,'Email corrected and the previous setup link was cancelled.');}
            catch(Throwable $mailError){flash('warning','Email corrected and the old setup link was cancelled, but the replacement email could not be queued: '.$mailError->getMessage());}
        }elseif($action==='delete_unused'){
            $id=(int)($_POST['user_id']??0);$confirmation=strtolower(trim((string)($_POST['confirmation_email']??'')));
            $pdo->beginTransaction();
            $stmt=$pdo->prepare('SELECT u.*,r.code role_code,r.name role_name FROM `' . table('users') . '` u JOIN `' . table('roles') . '` r ON r.id=u.role_id WHERE u.id=? FOR UPDATE');$stmt->execute([$id]);$target=$stmt->fetch();if(!$target)throw new RuntimeException('Staff account not found.');staff_assert_manageable($target,$isSuper,$currentUserId);
            if($id===$currentUserId)throw new RuntimeException('You cannot delete your own signed-in account.');
            if($confirmation!==strtolower((string)$target['email']))throw new RuntimeException('Type the account email exactly to confirm deletion.');
            if(!empty($target['email_verified_at'])||!empty($target['last_login_at']))throw new RuntimeException('Only unused, never-activated profiles can be deleted. Deactivate this account instead so historical records remain attributable.');
            $blockers=staff_reference_blockers($pdo,$id);if($blockers)throw new RuntimeException('This profile is already referenced by operational records and cannot be deleted. Deactivate it instead. References: '.implode(', ',array_slice($blockers,0,5)).(count($blockers)>5?'…':''));
            staff_cancel_open_invites($pdo,$id);$deletedEmail=(string)$target['email'];$deletedName=(string)$target['name'];$deletedRole=(string)$target['role_code'];
            audit_required('unused_staff_profile_deleted','user',$id,['deleted'=>true,'name'=>$deletedName,'email'=>$deletedEmail,'role'=>$deletedRole]);
            $delete=$pdo->prepare('DELETE FROM `' . table('users') . '` WHERE id=?');$delete->execute([$id]);if($delete->rowCount()!==1)throw new RuntimeException('The unused profile could not be deleted.');
            $pdo->commit();flash('success','Unused profile deleted. Its pending setup link was cancelled.');
        }elseif($action==='bulk_status'){
            $ids=array_values(array_unique(array_filter(array_map('intval',(array)($_POST['user_ids']??[])))));$status=(string)($_POST['bulk_status']??'');
            if(!$ids)throw new RuntimeException('Select at least one staff account.');
            if(!in_array($status,['active','inactive','locked'],true))throw new RuntimeException('Choose a valid account status.');
            $pdo->beginTransaction();
            foreach($ids as $id){$target=staff_target($pdo,$id);staff_assert_manageable($target,$isSuper,$currentUserId);if($id===$currentUserId&&$status!=='active')throw new RuntimeException('You cannot deactivate or lock your own signed-in account.');$pdo->prepare('UPDATE `' . table('users') . '` SET status=?,updated_at=NOW() WHERE id=?')->execute([$status,$id]);audit_required('staff_bulk_status_updated','user',$id,['status'=>$status],['status'=>$target['status']]);}
            $pdo->commit();flash('success',count($ids).' staff account'.(count($ids)===1?'':'s').' updated.');
        }else throw new RuntimeException('Unsupported staff action.');
        header('Location: staff.php');exit;
    }catch(Throwable $e){if($pdo->inTransaction())$pdo->rollBack();$error=$e->getMessage();}
}

$inviteSelect=$inviteReady
    ? ',i.id invite_id,i.expires_at invite_expires_at,i.used_at invite_used_at,i.last_sent_at,eo.status email_status,eo.attempts email_attempts,eo.last_error email_error,eo.sent_at email_sent_at'
    : ',NULL invite_id,NULL invite_expires_at,NULL invite_used_at,NULL last_sent_at,NULL email_status,NULL email_attempts,NULL email_error,NULL email_sent_at';
$inviteJoin=$inviteReady
    ? ' LEFT JOIN `' . table('staff_account_invites') . '` i ON i.id=(SELECT MAX(i2.id) FROM `' . table('staff_account_invites') . '` i2 WHERE i2.user_id=u.id) LEFT JOIN `' . table('email_outbox') . '` eo ON eo.id=i.email_outbox_id '
    : '';
$state=module_management_state();$q=$state['query'];$roleFilter=trim((string)($_GET['role']??''));$statusFilter=module_safe_status_filter('status',['','active','inactive','locked']);$branchFilter=max(0,(int)($_GET['branch_id']??0));
$validRoleCodes=array_column($roles,'code');$validRoleCodes[]='super_admin';if(!in_array($roleFilter,$validRoleCodes,true))$roleFilter='';
$where=['1=1'];$args=[];
if($q!==''){$term=module_like_term($q);if($structuredNameReady){$where[]='(u.name LIKE ? OR u.first_name LIKE ? OR u.middle_name LIKE ? OR u.last_name LIKE ? OR u.email LIKE ? OR r.name LIKE ? OR b.name LIKE ?)';array_push($args,$term,$term,$term,$term,$term,$term,$term);}else{$where[]='(u.name LIKE ? OR u.email LIKE ? OR r.name LIKE ? OR b.name LIKE ?)';array_push($args,$term,$term,$term,$term);}}
if($roleFilter!==''){$where[]='r.code=?';$args[]=$roleFilter;}if($statusFilter!==''){$where[]='u.status=?';$args[]=$statusFilter;}if($branchFilter>0){$where[]='u.branch_id=?';$args[]=$branchFilter;}
$whereSql=implode(' AND ',$where);
$count=$pdo->prepare('SELECT COUNT(*) FROM `' . table('users') . '` u JOIN `' . table('roles') . '` r ON r.id=u.role_id LEFT JOIN `' . table('branches') . '` b ON b.id=u.branch_id WHERE '.$whereSql);$count->execute($args);$total=(int)$count->fetchColumn();
$pages=max(1,(int)ceil($total/$state['per_page']));$state['page']=min($state['page'],$pages);$state['offset']=($state['page']-1)*$state['per_page'];
$nameSelect=$structuredNameReady?',u.first_name,u.middle_name,u.last_name':',NULL first_name,NULL middle_name,NULL last_name';
$baseSelect='SELECT u.id,u.branch_id,u.role_id,u.name'.$nameSelect.',u.email,u.status,u.email_verified_at,u.last_login_at,u.must_change_password,b.name branch_name,r.name role_name,r.code role_code'.$inviteSelect.' FROM `' . table('users') . '` u JOIN `' . table('roles') . '` r ON r.id=u.role_id LEFT JOIN `' . table('branches') . '` b ON b.id=u.branch_id'.$inviteJoin;
$nameOrder=$structuredNameReady?'u.last_name,u.first_name,u.middle_name':'u.name';
$stmt=$pdo->prepare($baseSelect.' WHERE '.$whereSql.' ORDER BY FIELD(r.code,"super_admin","clinic_admin","dentist","dental_assistant","receptionist","cashier","inventory_officer","staff"),'.$nameOrder.' LIMIT '.$state['per_page'].' OFFSET '.$state['offset']);$stmt->execute($args);$staff=$stmt->fetchAll();
$selectedStaff=null;if($state['manage_id']>0){$stmt=$pdo->prepare($baseSelect.' WHERE u.id=? LIMIT 1');$stmt->execute([$state['manage_id']]);$selectedStaff=$stmt->fetch()?:null;}
$mail=(array)app_config('mail',[]);$driver=(string)($mail['driver']??'smtp');
render_header('Staff Accounts & Roles');
?>
<div class="management-workspace">
<?php if($error):?><div class="flash error" data-auto-dismiss="10000"><?=e($error)?></div><?php endif;?>
<?php if(!$structuredNameReady):?><div class="alert warning"><strong>Version 0.5.9.8.32 is not applied.</strong> Structured first, middle, and last staff names are unavailable. <?php if($isSuper):?><a href="system_setup.php?tab=updates">Apply the update</a><?php endif;?></div><?php endif;?>
<?php if(!$inviteReady):?><div class="alert warning"><strong>Version 0.5.8.3 is not applied.</strong> Secure invitation and delivery tracking are disabled. <?php if($isSuper):?><a href="system_setup.php?tab=updates">Apply the update</a><?php endif;?></div><?php endif;?>
<?php if($driver==='log'):?><div class="alert warning"><strong>Email driver is Log only.</strong> Configure <a href="email_settings.php">Email &amp; SMTP</a> before sending setup links.</div><?php endif;?>
<div class="management-toolbar"><div><strong>Staff accounts and work assignments</strong><span class="muted">Administrators manage invitations, employee numbers, positions, branches, roles, and account status. Staff maintain personal contact details in Profile Center.</span></div><div class="inline-actions"><a class="btn ghost" href="email_templates.php">Email Templates</a><a class="btn ghost" href="email_settings.php">Email &amp; SMTP</a><button class="btn primary" data-open="staff-form" <?=($inviteReady&&$structuredNameReady)?'':'disabled'?>>Add staff member</button></div></div>
<form method="get" class="card management-filters"><label>Search staff<input type="search" name="q" value="<?=e($q)?>" placeholder="Name, email, role, or branch"></label><label>Role<select name="role"><option value="">All roles</option><?php if($isSuper):?><option value="super_admin" <?=$roleFilter==='super_admin'?'selected':''?>>Superuser</option><?php endif;?><?php foreach($roles as $roleRow):?><option value="<?=e((string)$roleRow['code'])?>" <?=$roleFilter===$roleRow['code']?'selected':''?>><?=e((string)$roleRow['name'])?></option><?php endforeach;?></select></label><label>Status<select name="status"><option value="">All</option><?php foreach(['active','inactive','locked'] as $status):?><option value="<?=$status?>" <?=$statusFilter===$status?'selected':''?>><?=e(module_status($status))?></option><?php endforeach;?></select></label><label>Branch<select name="branch_id"><option value="">All branches</option><?php foreach($branches as $branch):?><option value="<?=(int)$branch['id']?>" <?=$branchFilter===(int)$branch['id']?'selected':''?>><?=e((string)$branch['name'])?></option><?php endforeach;?></select></label><div class="filter-actions"><button class="btn primary">Filter</button><a class="btn ghost" href="staff.php">Reset</a></div></form>
<form method="post" class="card management-table-card" data-bulk-form><input type="hidden" name="csrf" value="<?=e(csrf_token())?>"><input type="hidden" name="action" value="bulk_status"><div class="card-head"><div><h2>Staff directory</h2><p>Activated accounts remain attributable to their historical clinical and financial activity.</p></div><?php module_render_list_count($total,'accounts');?></div><div class="management-bulk-bar" data-bulk-bar hidden><strong><span data-selected-count>0</span> selected</strong><div class="management-bulk-actions"><select name="bulk_status"><option value="active">Set active</option><option value="inactive">Set inactive</option><option value="locked">Lock accounts</option></select><button class="btn ghost" data-requires-selection>Apply status</button></div></div><?php module_render_server_pagination($total,$state['page'],$state['per_page'],'page','per_page','accounts');?><div class="table-wrap"><table><thead><tr><th class="management-checkbox"><input type="checkbox" data-select-all="input[data-row-select]"></th><th>Name</th><th>Role</th><th>Branch</th><th>Email</th><th>Account</th><th>Email delivery</th><th>Last login</th><th></th></tr></thead><tbody><?php foreach($staff as $row):$manageable=$row['role_code']!=='super_admin'&&($isSuper||$row['role_code']!=='clinic_admin');$emailStatus=(string)($row['email_status']??'');?><tr><td class="management-checkbox"><input type="checkbox" name="user_ids[]" value="<?=(int)$row['id']?>" data-row-select <?=$manageable?'':'disabled'?>></td><td><strong><?=e(user_full_name($row))?></strong><?php if($row['must_change_password']):?><small>Setup required</small><?php endif;?></td><td><?=e((string)$row['role_name'])?></td><td><?=e((string)($row['branch_name']??'All branches'))?></td><td><?=e((string)$row['email'])?><small><?=$row['email_verified_at']?'Verified':'Not activated'?></small></td><td><span class="badge"><?=e((string)$row['status'])?></span></td><td><?php if($emailStatus):?><span class="badge email-<?=e($emailStatus)?>"><?=e(email_delivery_label($emailStatus))?></span><?php if($row['email_sent_at']):?><small><?=e(date('M j, g:i A',strtotime((string)$row['email_sent_at'])))?></small><?php elseif($row['last_sent_at']):?><small>Attempted <?=e(date('M j, g:i A',strtotime((string)$row['last_sent_at'])))?></small><?php endif;?><?php else:?><span class="muted">No setup email</span><?php endif;?></td><td><?=$row['last_login_at']?e(date('M j, Y g:i A',strtotime((string)$row['last_login_at']))):'Never'?></td><td class="management-action-cell"><div class="inline-actions"><?php if($row['role_code']==='dentist'):?><a href="doctor_profile.php?user_id=<?=(int)$row['id']?>">Credentials</a><?php endif;?><?php if($manageable):?><a class="btn ghost compact" href="<?=e(module_management_url(['manage_id'=>(int)$row['id']]))?>">Manage</a><?php endif;?></div></td></tr><?php endforeach;?><?php if(!$staff):?><tr><td colspan="9" class="management-empty">No staff accounts match this view.</td></tr><?php endif;?></tbody></table></div><?php module_render_server_pagination($total,$state['page'],$state['per_page'],'page','per_page','accounts');?></form>
<?php if($selectedStaff):$manageable=$selectedStaff['role_code']!=='super_admin'&&($isSuper||$selectedStaff['role_code']!=='clinic_admin');$selectedRoleAssignments=staff_role_assignments($pdo,(int)$selectedStaff['id'],$selectedStaff);$selectedPrimaryRoleId=0;$selectedAdditionalRoleIds=[];$selectedAssignmentByRole=[];$selectedIsDentist=false;foreach($selectedRoleAssignments as $assignment){$selectedAssignmentByRole[(int)$assignment['id']]=$assignment;if((string)($assignment['code']??'')==='dentist')$selectedIsDentist=true;if(($assignment['assignment_type']??'')==='primary')$selectedPrimaryRoleId=(int)$assignment['id'];else $selectedAdditionalRoleIds[]=(int)$assignment['id'];}if($selectedPrimaryRoleId<=0)$selectedPrimaryRoleId=(int)$selectedStaff['role_id'];?><dialog id="manage-staff" class="management-dialog" data-auto-open-dialog="manage-staff"><div class="management-dialog-body"><button type="button" class="dialog-close" data-close>×</button><h2><?=e(user_full_name($selectedStaff))?></h2><div class="management-summary"><div><span>Role</span><strong><?=e((string)$selectedStaff['role_name'])?></strong></div><div><span>Branch</span><strong><?=e((string)($selectedStaff['branch_name']??'All branches'))?></strong></div><div><span>Account</span><strong><?=e(module_status((string)$selectedStaff['status']))?></strong></div><div><span>Last login</span><strong><?=$selectedStaff['last_login_at']?e(date('M j, Y g:i A',strtotime((string)$selectedStaff['last_login_at']))):'Never'?></strong></div></div><div class="inline-actions staff-role-badges"><?php foreach($selectedRoleAssignments as $assignment):?><span class="badge"><?=e((string)$assignment['name'])?> · <?=e(ucfirst((string)$assignment['assignment_type']))?></span><?php endforeach;?></div><div class="alert info"><strong>Single-profile boundary:</strong> personal name, contact, address, and emergency details belong to the staff member's Profile Center. This panel controls work assignment and account access only.</div><?php if($selectedIsDentist):?><div class="inline-actions"><a class="btn ghost" href="doctor_profile.php?user_id=<?=(int)$selectedStaff['id']?>">Professional credentials</a><a class="btn ghost" href="doctor_schedule.php?doctor_id=<?=(int)$selectedStaff['id']?>">Schedule</a></div><?php endif;?><?php if($manageable):?>
<form method="post" class="form-grid compact-form"><input type="hidden" name="csrf" value="<?=e(csrf_token())?>"><input type="hidden" name="action" value="update_employment"><input type="hidden" name="user_id" value="<?=(int)$selectedStaff['id']?>"><h3 class="wide">Work assignment</h3><label>Employee number<input name="employee_no" maxlength="30" value="<?=e((string)($selectedStaff['employee_no']??''))?>" <?=$employeeNoReady?'':'disabled'?>></label><label>Position / job title<input name="job_title" maxlength="120" value="<?=e((string)($selectedStaff['job_title']??''))?>" <?=$jobTitleReady?'':'disabled'?>></label><label>Primary branch<select name="branch_id"><option value="">All branches</option><?php foreach($branches as $branch):?><option value="<?=(int)$branch['id']?>" <?=(int)($selectedStaff['branch_id']??0)===(int)$branch['id']?'selected':''?>><?=e((string)$branch['name'])?></option><?php endforeach;?></select></label><?php if($selectedIsDentist):?><?php $selectedDentistProfile=dentist_profile((int)$selectedStaff['id']);?><label>Dentist employment type<select name="employment_type"><?php foreach(['full_time'=>'Full-time','part_time'=>'Part-time','visiting'=>'Visiting','contractual'=>'Contractual'] as $value=>$label):?><option value="<?=$value?>" <?=($selectedDentistProfile['employment_type']??'full_time')===$value?'selected':''?>><?=$label?></option><?php endforeach;?></select></label><?php endif;?><div class="wide"><button class="btn primary">Save work assignment</button></div></form>
<?php if($roleAssignmentsReady):?><form method="post" class="form-grid compact-form"><input type="hidden" name="csrf" value="<?=e(csrf_token())?>"><input type="hidden" name="action" value="update_roles"><input type="hidden" name="user_id" value="<?=(int)$selectedStaff['id']?>"><label>Primary role<select name="primary_role_id" required><?php foreach($roles as $roleRow):?><option value="<?=(int)$roleRow['id']?>" <?=$selectedPrimaryRoleId===(int)$roleRow['id']?'selected':''?>><?=e((string)$roleRow['name'])?></option><?php endforeach;?></select><small>Main dashboard after sign-in. Historical records remain attributed to the same user.</small></label><div class="wide"><strong>Additional roles available in the Task View selector</strong><p class="muted">Only checked, enabled, and unexpired roles appear. Permissions never merge, and patient or task context does not follow a switch. The selected Task View controls server-side access. Return to Superuser restores the protected owner workspace.</p><div class="role-assignment-control-list"><?php foreach($roles as $roleRow):$roleId=(int)$roleRow['id'];$assignment=(array)($selectedAssignmentByRole[$roleId]??[]);$isPrimaryChoice=$selectedPrimaryRoleId===$roleId;$checked=in_array($roleId,$selectedAdditionalRoleIds,true);$expiresValue=!empty($assignment['expires_at'])?date('Y-m-d',strtotime((string)$assignment['expires_at'])):'';$expired=!empty($assignment['expires_at'])&&strtotime((string)$assignment['expires_at'])<time();?><section class="role-assignment-control"><label class="role-assignment-toggle"><input type="checkbox" name="additional_role_ids[]" value="<?=$roleId?>" <?=$checked?'checked':''?> <?=$isPrimaryChoice?'disabled':''?>> <strong><?=e((string)$roleRow['name'])?></strong><?php if($isPrimaryChoice):?><span class="badge">Primary</span><?php elseif($expired):?><span class="badge warning">Expired</span><?php endif;?></label><label>Expires on<input type="date" name="role_expires_at[<?=$roleId?>]" value="<?=e($expiresValue)?>" <?=$isPrimaryChoice?'disabled':''?>><small>Leave blank for no expiration.</small></label><label>Assignment reason<input type="text" name="role_notes[<?=$roleId?>]" maxlength="255" value="<?=e((string)($assignment['note']??''))?>" placeholder="Why this role is required" <?=$isPrimaryChoice?'disabled':''?>></label><small><?php if(!empty($assignment['assigned_by_name'])):?>Assigned by <?=e((string)$assignment['assigned_by_name'])?><?php endif;?><?php if(!empty($assignment['assigned_at'])):?> · <?=e(date('M j, Y',strtotime((string)$assignment['assigned_at'])))?><?php endif;?><?php if(!empty($assignment['last_switched_at'])):?> · Last used <?=e(date('M j, Y g:i A',strtotime((string)$assignment['last_switched_at'])))?><?php endif;?></small></section><?php endforeach;?></div></div><div class="inline-actions wide"><button class="btn primary">Save role assignments</button></div></form><form method="post" class="compact-form"><input type="hidden" name="csrf" value="<?=e(csrf_token())?>"><input type="hidden" name="action" value="force_primary_role"><input type="hidden" name="user_id" value="<?=(int)$selectedStaff['id']?>"><button class="btn ghost" type="submit">Force return to primary role</button><small>Ends any stale role selection on the staff member's next request.</small></form><?php else:?><div class="alert warning"><strong>Dual roles unavailable.</strong> Apply Version 0.5.9.8.74 before adding additional staff roles.</div><?php endif;?><?php if($structuredNameReady&&!$selectedStaff['email_verified_at']&&!$selectedStaff['last_login_at']):?><form method="post" class="form-grid compact-form"><input type="hidden" name="csrf" value="<?=e(csrf_token())?>"><input type="hidden" name="action" value="update_name"><input type="hidden" name="user_id" value="<?=(int)$selectedStaff['id']?>"><h3 class="wide">Correct invitation identity</h3><label>First name<input name="first_name" maxlength="80" value="<?=e((string)($selectedStaff['first_name']??''))?>" required></label><label>Middle name<input name="middle_name" maxlength="80" value="<?=e((string)($selectedStaff['middle_name']??''))?>"></label><label>Last name<input name="last_name" maxlength="80" value="<?=e((string)($selectedStaff['last_name']??''))?>" required></label><button class="btn ghost">Correct invitation identity</button></form><?php elseif($structuredNameReady):?><p class="muted">This account is active or has been used. The staff member maintains personal identity and contact details in Profile Center; administrators should not maintain a second copy here.</p><?php endif;?><form method="post" class="compact-form"><input type="hidden" name="csrf" value="<?=e(csrf_token())?>"><input type="hidden" name="action" value="resend_invite"><input type="hidden" name="user_id" value="<?=(int)$selectedStaff['id']?>"><button class="btn ghost" <?=$inviteReady?'':'disabled'?>><?=$selectedStaff['last_sent_at']?'Resend setup link':'Send setup link'?></button></form><form method="post" class="compact-form"><input type="hidden" name="csrf" value="<?=e(csrf_token())?>"><input type="hidden" name="action" value="status"><input type="hidden" name="user_id" value="<?=(int)$selectedStaff['id']?>"><label>Status<select name="status"><?php foreach(['active','inactive','locked'] as $status):?><option value="<?=$status?>" <?=$selectedStaff['status']===$status?'selected':''?>><?=e(module_status($status))?></option><?php endforeach;?></select></label><button class="btn primary">Update status</button></form><?php if(!$selectedStaff['email_verified_at']&&!$selectedStaff['last_login_at']):?><form method="post" class="compact-form"><input type="hidden" name="csrf" value="<?=e(csrf_token())?>"><input type="hidden" name="action" value="correct_email"><input type="hidden" name="user_id" value="<?=(int)$selectedStaff['id']?>"><label>Correct invitation email<input type="email" name="new_email" value="<?=e((string)$selectedStaff['email'])?>" required></label><button class="btn ghost" <?=$inviteReady?'':'disabled'?>>Save and send new link</button></form><form method="post" class="compact-form"><input type="hidden" name="csrf" value="<?=e(csrf_token())?>"><input type="hidden" name="action" value="delete_unused"><input type="hidden" name="user_id" value="<?=(int)$selectedStaff['id']?>"><label>Delete unused account<input type="email" name="confirmation_email" placeholder="Type <?=e((string)$selectedStaff['email'])?>" required></label><button class="btn danger" data-confirm="Permanently delete this unused, never-activated account?">Delete unused account</button></form><?php else:?><p class="muted">Activated or previously used accounts cannot be deleted. Set the account inactive to preserve historical attribution.</p><?php endif;?><?php endif;?><a class="btn ghost" href="<?=e(module_management_url(['manage_id'=>null]))?>">Close</a></div></dialog><?php endif;?>
<dialog id="staff-form"><form method="post" class="form-grid"><button type="button" class="dialog-close" data-close>×</button><h2 class="wide">Add staff account</h2><input type="hidden" name="csrf" value="<?=e(csrf_token())?>"><input type="hidden" name="action" value="create"><label>First name<input name="first_name" maxlength="80" required></label><label>Middle name<input name="middle_name" maxlength="80"></label><label>Last name<input name="last_name" maxlength="80" required></label><label>Email<input type="email" name="email" required><small>A secure password-setup link will be sent here.</small></label><label>Role<select name="role_id" required><?php foreach($roles as $roleRow):?><option value="<?=(int)$roleRow['id']?>"><?=e((string)$roleRow['name'])?></option><?php endforeach;?></select></label><label>Branch<select name="branch_id"><option value="">All branches</option><?php foreach($branches as $branch):?><option value="<?=(int)$branch['id']?>"><?=e((string)$branch['name'])?></option><?php endforeach;?></select></label><button class="btn primary">Create and send setup link</button></form></dialog></div>
<?php render_footer();
