<?php
declare(strict_types=1);

require_once __DIR__ . '/gross-salary-report-common.php';

function psm_stmt(mysqli $con, string $sql): mysqli_stmt
{
    $stmt = $con->prepare($sql);
    if (!$stmt) throw new RuntimeException('Unable to prepare Pay Scale operation.');
    return $stmt;
}

function psm_rows(mysqli $con, string $sql, string $types = '', array $params = []): array
{
    $stmt = psm_stmt($con, $sql);
    if ($types !== '') $stmt->bind_param($types, ...$params);
    $stmt->execute();
    $rows = [];
    $result = $stmt->get_result();
    while ($row = $result->fetch_assoc()) $rows[] = $row;
    $stmt->close();
    return $rows;
}

function psm_columns(mysqli $con, string $table): array
{
    return gsr_columns($con, $table);
}

function psm_has_column(mysqli $con, string $table, string $column): bool
{
    return in_array($column, psm_columns($con, $table), true);
}

function psm_access(mysqli $con): array { return gsr_access($con); }

function psm_payrolls(mysqli $con, array $access): array
{
    return gsr_authorized_payrolls($con, $access);
}

function psm_id_in(array $rows, int $id): bool
{
    foreach ($rows as $row) if ((int)$row['id'] === $id) return true;
    return false;
}

function psm_options(mysqli $con): array
{
    return [
        'months' => psm_rows($con, 'SELECT id,month name FROM jos_app_mmonth ORDER BY id'),
        'years' => psm_rows($con, 'SELECT id,year name FROM jos_app_year ORDER BY id DESC LIMIT 3'),
        'salary_types' => psm_rows($con, 'SELECT id,name FROM jos_app_salarytype ORDER BY id'),
        'calculations' => psm_rows($con, 'SELECT id,calculation_type name FROM jos_app_calculation_type ORDER BY id'),
    ];
}

function psm_employee_scope(array $access, array &$where, string &$types, array &$params): void
{
    gsr_add_in('e.payroll_location', $access['payroll'], $where, $types, $params);
    gsr_add_in('e.branch_location', $access['branch'], $where, $types, $params);
    gsr_add_in('e.subbranch', $access['subbranch'], $where, $types, $params);
    gsr_add_in('e.addpayroll_location', $access['additional'], $where, $types, $params);
}

function psm_employees(mysqli $con, array $access, int $orgId = 0): array
{
    $where = ['e.status IN (0,1)']; $types = ''; $params = [];
    psm_employee_scope($access, $where, $types, $params);
    if ($orgId > 0) { $where[] = 'e.payroll_location=?'; $types .= 'i'; $params[] = $orgId; }
    return psm_rows($con, 'SELECT DISTINCT e.id,e.name,e.payroll_location FROM jos_app_employee e WHERE '.implode(' AND ', $where).' ORDER BY e.name,e.id LIMIT 5000', $types, $params);
}

function psm_defaults(): array
{
    return ['orgid'=>0,'empid'=>0,'configdata'=>0,'s_type'=>0,'month'=>(int)date('n'),'year'=>(int)date('Y'),'payscalename'=>'','total_salary'=>'','details'=>[]];
}

function psm_latest_record(mysqli $con, int $orgId, int $empId = 0): ?array
{
    if ($orgId < 1) return null;
    $sql = 'SELECT * FROM jos_app_payscale_nongrid WHERE orgid=?';
    $types = 'i'; $params = [$orgId];
    if ($empId > 0) { $sql .= ' AND empid=?'; $types .= 'i'; $params[] = $empId; }
    $sql .= " ORDER BY DATE(CONCAT(`year`,'-',LPAD(`month`,2,'0'),'-01')) DESC,id DESC LIMIT 1";
    $rows = psm_rows($con, $sql, $types, $params);
    return $rows[0] ?? null;
}

function psm_record(mysqli $con, int $id, array $payrolls): ?array
{
    if ($id < 1) return null;
    $rows = psm_rows($con, 'SELECT * FROM jos_app_payscale_nongrid WHERE id=? LIMIT 1', 'i', [$id]);
    $row = $rows[0] ?? null;
    if (!$row || !psm_id_in($payrolls, (int)$row['orgid'])) return null;
    return $row;
}

function psm_detail_rows(mysqli $con, int $payScaleId): array
{
    $map = [];
    foreach (psm_rows($con, 'SELECT * FROM jos_app_emp_payscale_config_grid WHERE payscaleid=? ORDER BY component_id,componenttype_id,id', 'i', [$payScaleId]) as $row) {
        $map[(int)$row['component_id']][(int)$row['componenttype_id']] = $row;
    }
    return $map;
}

function psm_components(mysqli $con, int $orgId): array
{
    if ($orgId < 1) return [];
    $sql = "SELECT so.componenttype section_id,so.componentname component_id,so.orderby,sm.componentname,sm.status,ct.componenttype section_name,ct.payscale_status payscale_enabled
            FROM jos_app_salaryorganisation so
            INNER JOIN jos_app_salarycomponentmaster sm ON sm.id=so.componentname
            INNER JOIN jos_app_componenttype ct ON ct.id=so.componenttype
            WHERE so.orgid=? AND so.componenttype IN (1,2,3,4,5)
            ORDER BY FIELD(so.componenttype,2,3,4,5,1),so.orderby,so.id";
    return psm_rows($con, $sql, 'i', [$orgId]);
}

function psm_percentage_options(array $components, int $section): array
{
    $allowed = [2=>[1,2,5,3],3=>[1,2,5,3,4],4=>[2,5,3,4],5=>[2,5,3,1]][$section] ?? [];
    return array_values(array_filter($components, static fn($r) => in_array((int)$r['section_id'], $allowed, true)));
}

function psm_defaults_for_components(mysqli $con, int $orgId): array
{
    $out = [];
    if ($orgId < 1) return $out;
    foreach (psm_rows($con, 'SELECT comp_id,field_name,value,readonly FROM jos_app_component_payscaleconfig WHERE orgid=?', 'i', [$orgId]) as $row) {
        $out[(int)$row['comp_id']][(int)$row['field_name']] = ['value'=>(string)$row['value'],'readonly'=>(int)$row['readonly']];
    }
    return $out;
}

function psm_post_details(array $components): array
{
    $valid = [];
    foreach ($components as $c) if ((int)$c['payscale_enabled']===1 && in_array((int)$c['section_id'],[2,3,4,5],true)) $valid[(int)$c['component_id']] = (int)$c['section_id'];
    $details = [];
    foreach ((array)($_POST['details'] ?? []) as $componentId => $row) {
        $componentId = (int)$componentId;
        if (!isset($valid[$componentId]) || !is_array($row)) continue;
        $percentage = array_values(array_unique(array_filter(array_map('intval', (array)($row['percentageof'] ?? [])), static fn($v)=>$v>0)));
        $details[$valid[$componentId]][$componentId] = [
            'calculation'=>(int)($row['calculation'] ?? 0),
            'percentageof'=>$percentage,
            'value_amt'=>trim((string)($row['value_amt'] ?? '0')),
        ];
    }
    return $details;
}

function psm_post(array $components): array
{
    return [
        'orgid'=>max(0,(int)($_POST['orgid']??0)), 'empid'=>max(0,(int)($_POST['empid']??0)),
        'configdata'=>(int)($_POST['configdata']??0), 's_type'=>max(0,(int)($_POST['s_type']??0)),
        'month'=>max(0,(int)($_POST['month']??0)), 'year'=>max(0,(int)($_POST['year']??0)),
        'payscalename'=>trim((string)($_POST['payscalename']??'')),
        'total_salary'=>trim((string)($_POST['total_salary']??'')), 'details'=>psm_post_details($components),
    ];
}

function psm_number(string $value, bool $allowNegative = false): ?string
{
    if ($value === '' || !is_numeric($value)) return null;
    $number = (float)$value;
    if (!is_finite($number) || (!$allowNegative && $number < 0)) return null;
    return number_format($number, 2, '.', '');
}

function psm_validate(mysqli $con, array &$v, int $id, array $payrolls, array $employees, array $opts, array $components): array
{
    $errors = [];
    if (!psm_id_in($payrolls, $v['orgid'])) $errors[] = 'Select a valid and authorized Organisation Name.';
    if ($v['empid'] && !psm_id_in($employees, $v['empid'])) $errors[] = 'Select a valid and authorized Employee Name.';
    if (!in_array($v['configdata'], [0,1], true)) $errors[] = 'Select a valid Configure Data value.';
    foreach ([['s_type','salary_types','Gross Type'],['month','months','Month'],['year','years','Year']] as [$key,$list,$label]) if (!psm_id_in($opts[$list], (int)$v[$key])) $errors[] = $label.' is required.';
    if ($v['payscalename'] === '') $errors[] = 'Pay Scale Name is required.';
    elseif (mb_strlen($v['payscalename']) > 255) $errors[] = 'Pay Scale Name is too long.';
    $gross = psm_number($v['total_salary']);
    if ($gross === null) $errors[] = 'Gross Pay must be a valid non-negative amount.'; else $v['total_salary'] = $gross;
    $stmt = psm_stmt($con, 'SELECT id FROM jos_app_payscale_nongrid WHERE LOWER(TRIM(payscalename))=LOWER(TRIM(?)) AND id<>? LIMIT 1');
    $stmt->bind_param('si', $v['payscalename'], $id); $stmt->execute();
    if ($stmt->get_result()->fetch_assoc()) $errors[] = 'Pay Scale Name already exists.'; $stmt->close();

    $componentMap = []; foreach ($components as $c) if ((int)$c['payscale_enabled']===1 && in_array((int)$c['section_id'],[2,3,4,5],true)) $componentMap[(int)$c['component_id']] = $c;
    $seen = [];
    foreach ($v['details'] as $section => &$rows) foreach ($rows as $componentId => &$row) {
        if (isset($seen[$componentId])) { $errors[] = 'A salary component cannot be added more than once.'; continue; }
        $seen[$componentId] = true;
        if (!isset($componentMap[$componentId]) || (int)$componentMap[$componentId]['section_id'] !== (int)$section) { $errors[] = 'An invalid salary component was submitted.'; continue; }
        if ((int)$row['calculation'] !== 0 && !psm_id_in($opts['calculations'], (int)$row['calculation'])) $errors[] = 'Select a valid calculation type for '.$componentMap[$componentId]['componentname'].'.';
        if ((string)$row['value_amt'] !== '') {
            $amount = psm_number((string)$row['value_amt'], true);
            if ($amount === null) $errors[] = 'Enter a valid numeric value for '.$componentMap[$componentId]['componentname'].'.'; else $row['value_amt'] = $amount;
        }
        $allowed = []; foreach (psm_percentage_options($components, (int)$section) as $o) $allowed[(int)$o['component_id']] = true;
        foreach ($row['percentageof'] as $sourceId) if (!isset($allowed[$sourceId]) || $sourceId === $componentId) $errors[] = 'Invalid Percentage Of component for '.$componentMap[$componentId]['componentname'].'.';
        if ((int)$row['calculation'] === 1) $row['percentageof'] = [];
    }
    unset($rows,$row);
    if (!$componentMap) $errors[] = 'No Pay Scale-enabled salary components are configured for this organisation.';
    if (!$errors && psm_gross_validation_enabled($con)) {
        $calculated = psm_calculated_earnings($v);
        if (abs($calculated-(float)$v['total_salary']) > 0.005) $errors[] = 'Earning total does not match Gross Pay.';
    }
    return array_values(array_unique($errors));
}

function psm_calculated_earnings(array $v): float
{
    $computed=[];$sum=0.0;
    foreach($v['details'][2]??[] as $componentId=>$row){$amount=(float)$row['value_amt'];$value=$amount;if((int)$row['calculation']===3&&!empty($row['percentageof'])){$base=0.0;foreach($row['percentageof'] as $sourceId)$base+=(int)$sourceId===28?(float)$v['total_salary']:($computed[(int)$sourceId]??0.0);$value=($base/100)*$amount;}$computed[(int)$componentId]=$value;$sum+=$value;}
    return round($sum,2);
}

function psm_gross_validation_enabled(mysqli $con): bool
{
    $rows = psm_rows($con, 'SELECT grosspay_valid FROM jos_app_usersetting ORDER BY id LIMIT 1');
    return (int)($rows[0]['grosspay_valid'] ?? 0) !== 1;
}

function psm_save(mysqli $con, array $v, int $id): int
{
    $isEdit = $id > 0; $userId = (int)(current_user()['id'] ?? 0);
    $con->begin_transaction();
    try {
        $today = date('Y-m-d');
        if ($isEdit) {
            $stmt = psm_stmt($con, 'UPDATE jos_app_payscale_nongrid SET date=?,orgid=?,month=?,year=?,payscalename=?,total_salary=?,empid=?,s_type=?,configdata=? WHERE id=?');
            $stmt->bind_param('siiissiiii', $today,$v['orgid'],$v['month'],$v['year'],$v['payscalename'],$v['total_salary'],$v['empid'],$v['s_type'],$v['configdata'],$id);
        } else {
            $stmt = psm_stmt($con, 'INSERT INTO jos_app_payscale_nongrid (date,orgid,month,year,payscalename,total_salary,empid,s_type,configdata,status,scalelock) VALUES (?,?,?,?,?,?,?,?,?,0,0)');
            $stmt->bind_param('siiissiii', $today,$v['orgid'],$v['month'],$v['year'],$v['payscalename'],$v['total_salary'],$v['empid'],$v['s_type'],$v['configdata']);
        }
        $stmt->execute(); if (!$isEdit) $id = (int)$stmt->insert_id; $stmt->close();
        $stmt = psm_stmt($con, 'DELETE FROM jos_app_emp_payscale_config_grid WHERE payscaleid=?'); $stmt->bind_param('i',$id); $stmt->execute(); $stmt->close();
        foreach ($v['details'] as $section => $rows) foreach ($rows as $componentId => $row) {
            $percentage = (int)$row['calculation'] === 1 ? '0' : (implode(',', $row['percentageof']) ?: '0');
            $stmt = psm_stmt($con, 'INSERT INTO jos_app_emp_payscale_config_grid (date,payscaleid,payscale_name,gross_pay,payrollorgid,component_id,componenttype_id,calculation,value_amt,percentageof) VALUES (?,?,?,?,?,?,?,?,?,?)');
            $stmt->bind_param('sissiiiiis',$today,$id,$v['payscalename'],$v['total_salary'],$v['orgid'],$section,$componentId,$row['calculation'],$row['value_amt'],$percentage); $stmt->execute();
            $stmt->close();
            $stmt = psm_stmt($con, 'INSERT INTO jos_app_emp_payscale_config_grid_editlog (date,payid,payscaleid,payrollorgid,payscale_name,gross_pay,componenttype_id,component_id,calculation,value_amt,percentageof,modify_by,month,year) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)');
            $stmt->bind_param('siiissiiiisiii',$today,$id,$id,$v['orgid'],$v['payscalename'],$v['total_salary'],$componentId,$section,$row['calculation'],$row['value_amt'],$percentage,$userId,$v['month'],$v['year']); $stmt->execute(); $stmt->close();
        }
        if ($v['empid'] > 0) {
            $stmt=psm_stmt($con,'UPDATE jos_app_employee SET payscale=? WHERE id=? AND payroll_location=?');$stmt->bind_param('iii',$id,$v['empid'],$v['orgid']);$stmt->execute();if($stmt->affected_rows<0)throw new RuntimeException('Employee assignment failed.');$stmt->close();
            $gross='0.00';$day='0.00';$hour='0.00'; if(in_array($v['s_type'],[1,5],true))$gross=$v['total_salary'];elseif(in_array($v['s_type'],[2,3],true))$day=$v['total_salary'];else$hour=$v['total_salary'];
            $existing=psm_rows($con,'SELECT id FROM jos_app_emp_gross_pay WHERE empid=? AND month=? AND year=? AND orgid=? LIMIT 1','iiii',[$v['empid'],$v['month'],$v['year'],$v['orgid']]);
            if($existing){$gid=(int)$existing[0]['id'];$stmt=psm_stmt($con,'UPDATE jos_app_emp_gross_pay SET s_type=?,grosspay=?,rateperday=?,rateperhr=? WHERE id=?');$stmt->bind_param('isssi',$v['s_type'],$gross,$day,$hour,$gid);}else{$stmt=psm_stmt($con,'INSERT INTO jos_app_emp_gross_pay (empid,month,year,orgid,s_type,grosspay,rateperday,rateperhr) VALUES (?,?,?,?,?,?,?,?)');$stmt->bind_param('iiiiisss',$v['empid'],$v['month'],$v['year'],$v['orgid'],$v['s_type'],$gross,$day,$hour);}$stmt->execute();$stmt->close();
        }
        $con->commit(); return $id;
    } catch (Throwable $e) { $con->rollback(); throw $e; }
}

function psm_status_action(mysqli $con, int $id, string $action): void
{
    $map=['verify'=>[1,0],'approve'=>[2,1],'unlock'=>[-1,0]]; if(!isset($map[$action]))throw new RuntimeException('Invalid action.');
    [$status,$lock]=$map[$action];$uid=(int)(current_user()['id']??0);$con->begin_transaction();
    try{if($action==='unlock'){$stmt=psm_stmt($con,'UPDATE jos_app_payscale_nongrid SET scalelock=0 WHERE id=? AND scalelock=1');$stmt->bind_param('i',$id);}else{$expected=$action==='verify'?0:1;$stmt=psm_stmt($con,'UPDATE jos_app_payscale_nongrid SET status=?,scalelock=? WHERE id=? AND status=? AND scalelock=0');$stmt->bind_param('iiii',$status,$lock,$id,$expected);}$stmt->execute();if($stmt->affected_rows!==1)throw new RuntimeException('The Pay Scale state has changed.');$stmt->close();$stmt=psm_stmt($con,'INSERT INTO jos_app_payscale_statuslog (date,payscaleid,status,userid) VALUES (NOW(),?,?,?)');$stmt->bind_param('iii',$id,$status,$uid);$stmt->execute();$stmt->close();$con->commit();}catch(Throwable $e){$con->rollback();throw $e;}
}

function psm_status_label(int $status, int $locked): array
{
    if ($locked) return ['Approved & Locked','bg-emerald-50 text-emerald-700'];
    return $status===1?['Verified','bg-amber-50 text-amber-700']:($status===2?['Approved / Unlocked','bg-blue-50 text-blue-700']:['Unverified','bg-slate-100 text-slate-600']);
}
