<?php
declare(strict_types=1);

function db(): PDO
{
    static $pdo = null;
    global $config;
    if ($pdo instanceof PDO) {
        return $pdo;
    }
    $dsn = sprintf('mysql:host=%s;port=%s;dbname=%s;charset=utf8mb4', $config['db_host'], $config['db_port'], $config['db_name']);
    $pdo = new PDO($dsn, $config['db_user'], $config['db_pass'], [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_EMULATE_PREPARES => false,
    ]);
    return $pdo;
}

function e(?string $value): string
{
    return htmlspecialchars((string)$value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}

function initial(string $value): string
{
    return function_exists('mb_substr') ? mb_substr($value, 0, 1, 'UTF-8') : substr($value, 0, 1);
}

function app_url(string $path = ''): string
{
    global $config;
    $base = rtrim((string)($config['app_url'] ?? ''), '/');
    return $base . ($path !== '' ? '/' . ltrim($path, '/') : '');
}

function redirect(string $path): never
{
    header('Location: ' . app_url($path));
    exit;
}

function csrf_token(): string
{
    if (empty($_SESSION['csrf_token'])) {
        $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
    }
    return $_SESSION['csrf_token'];
}

function csrf_field(): string
{
    return '<input type="hidden" name="csrf_token" value="' . e(csrf_token()) . '">';
}

function verify_csrf(): void
{
    $token = (string)($_POST['csrf_token'] ?? '');
    if ($token === '' || !hash_equals((string)($_SESSION['csrf_token'] ?? ''), $token)) {
        http_response_code(419);
        exit('انتهت صلاحية الجلسة. حدّث الصفحة وحاول مرة أخرى.');
    }
}

function user(): ?array
{
    return $_SESSION['user'] ?? null;
}

function require_auth(): void
{
    if (!user()) {
        redirect('login.php');
    }
}

function company_id(): int
{
    return (int)(user()['company_id'] ?? 0);
}

function has_permission(string $code): bool
{
    $current = user();
    if (!$current) return false;
    if (($current['role_code'] ?? '') === 'SYSTEM_ADMIN') return true;
    $stmt = db()->prepare('SELECT COUNT(*) FROM user_roles ur JOIN roles r ON r.id=ur.role_id JOIN role_permissions rp ON rp.role_id=r.id JOIN permissions p ON p.id=rp.permission_id WHERE ur.user_id=? AND r.company_id=? AND r.is_active=1 AND p.code=? AND (ur.starts_at IS NULL OR ur.starts_at<=NOW()) AND (ur.ends_at IS NULL OR ur.ends_at>=NOW())');
    $stmt->execute([(int)$current['id'], company_id(), $code]);
    return (int)$stmt->fetchColumn() > 0;
}

function require_permission(string $code): void
{
    require_auth();
    if (!has_permission($code)) {
        http_response_code(403);
        exit('ليس لديك صلاحية للوصول إلى هذه الصفحة.');
    }
}

function flash(string $type, string $message): void
{
    $_SESSION['flash'] = ['type' => $type, 'message' => $message];
}

function take_flash(): ?array
{
    $flash = $_SESSION['flash'] ?? null;
    unset($_SESSION['flash']);
    return $flash;
}

function audit(string $action, string $entityType, ?string $entityId = null, array $details = []): void
{
    $current = user();
    if (!$current) {
        return;
    }
    $stmt = db()->prepare('INSERT INTO audit_logs (company_id,user_id,event_type,object_type,object_id,event_description,new_values,ip_address,user_agent,created_at) VALUES (?,?,?,?,?,?,?,?,?,NOW())');
    $stmt->execute([
        (int)$current['company_id'],
        (int)$current['id'],
        $action, $entityType, $entityId, $action,
        json_encode($details, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
        substr((string)($_SERVER['REMOTE_ADDR'] ?? ''), 0, 64),
        substr((string)($_SERVER['HTTP_USER_AGENT'] ?? ''), 0, 1000),
    ]);
}

function scalar(string $sql, array $params = []): mixed
{
    $stmt = db()->prepare($sql);
    $stmt->execute($params);
    return $stmt->fetchColumn();
}

function mapped_account(string $roleCode): int
{
    $stmt = db()->prepare('SELECT cam.account_id FROM company_account_mappings cam JOIN account_roles ar ON ar.id=cam.account_role_id JOIN accounts a ON a.id=cam.account_id WHERE cam.company_id=? AND ar.role_code=? AND cam.branch_id IS NULL AND cam.project_id IS NULL AND a.status=\'active\' AND cam.effective_from<=CURDATE() AND (cam.effective_to IS NULL OR cam.effective_to>=CURDATE()) ORDER BY cam.effective_from DESC,cam.id DESC LIMIT 1');
    $stmt->execute([company_id(),$roleCode]); $id=(int)$stmt->fetchColumn();
    if(!$id) throw new RuntimeException('يلزم ربط الحساب النظامي: '.$roleCode.' من صفحة الربط المحاسبي.');
    return $id;
}

function post_system_journal(string $date,string $description,string $sourceType,int $sourceId,array $lines): int
{
    $pdo=db();$companyId=company_id();$userId=(int)user()['id'];
    $existing=(int)scalar('SELECT id FROM journal_entries WHERE company_id=? AND source_type=? AND source_id=? AND status=\'posted\' LIMIT 1',[$companyId,$sourceType,$sourceId]);
    if($existing) return $existing;
    $debit=0.0;$credit=0.0;foreach($lines as $line){$debit+=round((float)($line['debit']??0),2);$credit+=round((float)($line['credit']??0),2);}
    if(!$lines||abs($debit-$credit)>0.009||$debit<=0) throw new RuntimeException('تعذر إنشاء القيد الآلي لأنه غير متوازن.');
    $periodStmt=$pdo->prepare("SELECT fp.id FROM fiscal_periods fp JOIN fiscal_years fy ON fy.id=fp.fiscal_year_id WHERE fy.company_id=? AND ? BETWEEN fp.starts_on AND fp.ends_on AND fp.status='open' LIMIT 1");$periodStmt->execute([$companyId,$date]);$periodId=(int)$periodStmt->fetchColumn();if(!$periodId)throw new RuntimeException('لا توجد فترة مالية مفتوحة لتاريخ المستند.');
    $bookId=(int)scalar("SELECT id FROM journal_books WHERE company_id=? AND book_type='general' LIMIT 1",[$companyId]);if(!$bookId){$pdo->prepare("INSERT INTO journal_books(company_id,code,name_ar,book_type,is_active) VALUES (?,'GJ','اليومية العامة','general',1)")->execute([$companyId]);$bookId=(int)$pdo->lastInsertId();}
    $currencyId=(int)scalar('SELECT base_currency_id FROM companies WHERE id=?',[$companyId]);$entryNo='AUTO-'.date('Ymd',strtotime($date)).'-'.str_pad((string)((int)scalar('SELECT COUNT(*)+1 FROM journal_entries WHERE company_id=?',[$companyId])),6,'0',STR_PAD_LEFT);
    $stmt=$pdo->prepare("INSERT INTO journal_entries(company_id,branch_id,fiscal_period_id,journal_book_id,entry_no,entry_date,description,currency_id,source_type,source_id,idempotency_key,status,created_by,approved_by,approved_at,posted_by,posted_at,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,NOW(),?,NOW(),NOW(),NOW())");$stmt->execute([$companyId,(int)user()['branch_id'],$periodId,$bookId,$entryNo,$date,$description,$currencyId,$sourceType,$sourceId,$sourceType.':'.$sourceId,'posted',$userId,$userId,$userId]);$entryId=(int)$pdo->lastInsertId();
    $insert=$pdo->prepare('INSERT INTO journal_lines(journal_entry_id,line_no,account_id,description,debit_amount,credit_amount,project_id,cost_center_id,cost_code_id,created_at) VALUES (?,?,?,?,?,?,?,?,?,NOW())');foreach($lines as $i=>$line)$insert->execute([$entryId,$i+1,(int)$line['account_id'],$line['description']??$description,round((float)($line['debit']??0),2),round((float)($line['credit']??0),2),($line['project_id']??0)?:null,($line['cost_center_id']??0)?:null,($line['cost_code_id']??0)?:null]);
    return $entryId;
}

function active_page(string $page, string $current): string
{
    return $page === $current ? 'active' : '';
}
