<?php

require_once __DIR__ . '/../db.php';
require_once __DIR__ . '/locations.php';
require_once __DIR__ . '/settings_extensions.php';

function settings_require_auth(): void
{
    if (session_status() !== PHP_SESSION_ACTIVE) {
        session_start();
    }

    if (empty($_SESSION['authenticated'])) {
        header('Location: signin.php');
        exit();
    }
}

function settings_ensure_tables(mysqli $conn): void
{
    foreach (array_merge(settings_schema_queries(), settings_extension_schema_queries()) as $query) {
        $conn->query($query);
    }
}

function settings_schema_queries(): array
{
    return [
        "CREATE TABLE IF NOT EXISTS organization_settings (
            id INT AUTO_INCREMENT PRIMARY KEY,
            organization_id INT NOT NULL,
            logo_path VARCHAR(255) DEFAULT NULL,
            date_format VARCHAR(30) DEFAULT 'dd/MM/yyyy',
            field_separator VARCHAR(5) DEFAULT '/',
            primary_contact_name VARCHAR(150) DEFAULT NULL,
            primary_contact_email VARCHAR(150) DEFAULT NULL,
            sender_email VARCHAR(150) DEFAULT 'message-service@mail.zohopayroll.in',
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            UNIQUE KEY uniq_org_settings (organization_id)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
        "CREATE TABLE IF NOT EXISTS branding_preferences (
            id INT AUTO_INCREMENT PRIMARY KEY,
            organization_id INT NOT NULL,
            appearance ENUM('dark', 'light') NOT NULL DEFAULT 'dark',
            accent_color VARCHAR(30) NOT NULL DEFAULT 'blue',
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            UNIQUE KEY uniq_branding_org (organization_id)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
        "CREATE TABLE IF NOT EXISTS work_locations (
            id INT AUTO_INCREMENT PRIMARY KEY,
            organization_id INT NOT NULL,
            location_name VARCHAR(150) NOT NULL,
            address_line1 VARCHAR(200) DEFAULT NULL,
            address_line2 VARCHAR(200) DEFAULT NULL,
            state VARCHAR(100) DEFAULT NULL,
            city VARCHAR(100) DEFAULT NULL,
            pincode VARCHAR(20) DEFAULT NULL,
            is_filing_address TINYINT(1) NOT NULL DEFAULT 0,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            UNIQUE KEY uniq_org_location_name (organization_id, location_name)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
        "CREATE TABLE IF NOT EXISTS departments (
            id INT AUTO_INCREMENT PRIMARY KEY,
            org_id VARCHAR(50) NOT NULL,
            name VARCHAR(120) NOT NULL,
            code VARCHAR(80) DEFAULT NULL,
            description VARCHAR(255) DEFAULT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            UNIQUE KEY uniq_org_department_name (org_id, name)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
        "CREATE TABLE IF NOT EXISTS designations (
            id INT AUTO_INCREMENT PRIMARY KEY,
            organization_id INT NOT NULL,
            name VARCHAR(120) NOT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            UNIQUE KEY uniq_org_designation (organization_id, name)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
        "CREATE TABLE IF NOT EXISTS subscription_details (
            id INT AUTO_INCREMENT PRIMARY KEY,
            organization_id INT NOT NULL,
            plan_name VARCHAR(120) NOT NULL DEFAULT '14-DAYS FREE TRIAL',
            billing_cycle VARCHAR(30) NOT NULL DEFAULT 'monthly',
            monthly_price DECIMAL(10,2) NOT NULL DEFAULT 0.00,
            employee_limit INT NOT NULL DEFAULT 0,
            status VARCHAR(30) NOT NULL DEFAULT 'trial',
            trial_ends_on DATE DEFAULT NULL,
            feature_summary TEXT DEFAULT NULL,
            premium_feature_summary TEXT DEFAULT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            UNIQUE KEY uniq_org_subscription (organization_id)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
        "CREATE TABLE IF NOT EXISTS settings_roles (
            id INT AUTO_INCREMENT PRIMARY KEY,
            organization_id INT NOT NULL,
            role_name VARCHAR(150) NOT NULL,
            description VARCHAR(500) DEFAULT NULL,
            is_system TINYINT(1) NOT NULL DEFAULT 0,
            permissions_json LONGTEXT DEFAULT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            UNIQUE KEY uniq_org_role (organization_id, role_name)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
        "CREATE TABLE IF NOT EXISTS settings_users (
            id INT AUTO_INCREMENT PRIMARY KEY,
            organization_id INT NOT NULL,
            full_name VARCHAR(180) NOT NULL,
            email VARCHAR(190) NOT NULL,
            role_name VARCHAR(150) NOT NULL DEFAULT 'Admin',
            status VARCHAR(30) NOT NULL DEFAULT 'ACTIVE',
            avatar_path VARCHAR(255) DEFAULT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            UNIQUE KEY uniq_org_user_email (organization_id, email)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
        "CREATE TABLE IF NOT EXISTS pay_schedules (
            id INT AUTO_INCREMENT PRIMARY KEY,
            organization_id INT NOT NULL,
            pay_frequency VARCHAR(100) NOT NULL DEFAULT 'Every month',
            working_days_json TEXT DEFAULT NULL,
            pay_day_label VARCHAR(120) DEFAULT NULL,
            first_pay_period VARCHAR(60) DEFAULT NULL,
            first_pay_date DATE DEFAULT NULL,
            working_days_count INT NOT NULL DEFAULT 0,
            upcoming_payrolls_json LONGTEXT DEFAULT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            UNIQUE KEY uniq_org_pay_schedule (organization_id)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
        "CREATE TABLE IF NOT EXISTS statutory_components (
            id INT AUTO_INCREMENT PRIMARY KEY,
            organization_id INT NOT NULL,
            component_code VARCHAR(40) NOT NULL,
            is_enabled TINYINT(1) NOT NULL DEFAULT 0,
            config_json LONGTEXT DEFAULT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            UNIQUE KEY uniq_org_component (organization_id, component_code)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
        "CREATE TABLE IF NOT EXISTS salary_templates (
            id INT AUTO_INCREMENT PRIMARY KEY,
            organization_id INT NOT NULL,
            template_code VARCHAR(60) NOT NULL DEFAULT 'standard',
            template_name VARCHAR(150) NOT NULL DEFAULT 'Standard Salary Template',
            annual_ctc DECIMAL(14,2) NOT NULL DEFAULT 0.00,
            basic_percent DECIMAL(6,2) NOT NULL DEFAULT 50.00,
            hra_enabled TINYINT(1) NOT NULL DEFAULT 0,
            hra_percent DECIMAL(6,2) NOT NULL DEFAULT 50.00,
            conveyance_enabled TINYINT(1) NOT NULL DEFAULT 0,
            conveyance_monthly DECIMAL(14,2) NOT NULL DEFAULT 0.00,
            is_default TINYINT(1) NOT NULL DEFAULT 1,
            status VARCHAR(30) NOT NULL DEFAULT 'Draft',
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            UNIQUE KEY uniq_org_template_code (organization_id, template_code)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
        "CREATE TABLE IF NOT EXISTS salary_components (
            id INT AUTO_INCREMENT PRIMARY KEY,
            organization_id INT NOT NULL,
            component_group VARCHAR(40) NOT NULL,
            component_kind VARCHAR(40) NOT NULL,
            component_key VARCHAR(120) NOT NULL,
            name_in_payslip VARCHAR(150) NOT NULL,
            display_label VARCHAR(150) NOT NULL,
            calculation_type VARCHAR(150) DEFAULT NULL,
            frequency_label VARCHAR(150) DEFAULT NULL,
            consider_for_epf TINYINT(1) NOT NULL DEFAULT 0,
            consider_for_esi TINYINT(1) NOT NULL DEFAULT 0,
            is_active TINYINT(1) NOT NULL DEFAULT 1,
            max_amount DECIMAL(14,2) DEFAULT NULL,
            extra_json LONGTEXT DEFAULT NULL,
            display_order INT NOT NULL DEFAULT 0,
            is_default TINYINT(1) NOT NULL DEFAULT 0,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            UNIQUE KEY uniq_org_salary_component (organization_id, component_group, component_key),
            KEY idx_salary_component_group (organization_id, component_group),
            KEY idx_salary_component_kind (component_kind)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
        "CREATE TABLE IF NOT EXISTS setup_checklist_states (
            id INT AUTO_INCREMENT PRIMARY KEY,
            organization_id INT NOT NULL,
            step_key VARCHAR(80) NOT NULL,
            is_completed TINYINT(1) NOT NULL DEFAULT 0,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            UNIQUE KEY uniq_org_setup_step (organization_id, step_key)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
    ];
}

function settings_missing_tables(mysqli $conn): array
{
    $required = [
        'organization_settings',
        'branding_preferences',
        'work_locations',
        'designations',
        'subscription_details',
        'settings_users',
        'settings_roles',
        'pay_schedules',
        'statutory_components',
        'salary_templates',
        'salary_components',
    ];
    $missing = [];

    foreach ($required as $table) {
        $stmt = $conn->prepare("SHOW TABLES LIKE ?");
        if (!$stmt) {
            $missing[] = $table;
            continue;
        }
        $stmt->bind_param('s', $table);
        $stmt->execute();
        $result = $stmt->get_result();
        if (!$result || $result->num_rows === 0) {
            $missing[] = $table;
        }
        $stmt->close();
    }

    return $missing;
}

function settings_subscription_feature_sets(): array
{
    return [
        'core' => [
            'Reporting Tags',
            'Document Module',
            'Leave Management',
            'Basic Attendance Management',
            'Advanced Approval',
            'Salary Templates',
            'Letter Templates',
            'Salary Hold',
            'Custom Salary Components',
            'Approvals',
            'Reminder',
            'Loans',
            'Forms',
            'Activity Logs',
            'Audit Trail',
            'Salary Components Custom Formula',
            'Custom Alerts And Reminders',
            'Contractors',
            'New Joinee Arrears',
            'Employee Statutory Bonus',
            'Bonus Pay Run',
            'Off Cycle Payroll',
            'Donation',
            'Custom View',
            'Scheduled One Time Earnings',
            'Custom Fields',
            'Custom Roles',
            'Workflow Rules',
            'Custom Functions',
            'WebTabs',
        ],
        'premium' => [
            'Custom Buttons and Links',
        ],
    ];
}

function settings_subscription_feature_text(array $features): string
{
    return implode("\n", $features);
}

function settings_parse_feature_text(?string $text): array
{
    return array_values(array_filter(array_map('trim', explode("\n", (string) $text))));
}

function settings_merge_feature_list(array $existing, array $defaults): array
{
    $merged = [];
    $seen = [];

    foreach (array_merge($existing, $defaults) as $feature) {
        $feature = trim((string) $feature);
        if ($feature === '') {
            continue;
        }

        $key = strtolower($feature);
        if (isset($seen[$key])) {
            continue;
        }

        $seen[$key] = true;
        $merged[] = $feature;
    }

    return $merged;
}

function settings_get_current_org(mysqli $conn): array
{
    $sessionOrgId = $_SESSION['org_id'] ?? null;
    $org = null;

    if ($sessionOrgId !== null && $sessionOrgId !== '') {
        $stmt = $conn->prepare(
            "SELECT * FROM organizations
             WHERE id = ? OR org_id = ?
             ORDER BY id DESC
             LIMIT 1"
        );
        if ($stmt) {
            $sessionOrgIdString = (string) $sessionOrgId;
            $sessionOrgIdInt = ctype_digit($sessionOrgIdString) ? (int) $sessionOrgIdString : 0;
            $stmt->bind_param('is', $sessionOrgIdInt, $sessionOrgIdString);
            $stmt->execute();
            $result = $stmt->get_result();
            $org = $result ? $result->fetch_assoc() : null;
            $stmt->close();
        }
    }

    if (!$org) {
        $result = $conn->query("SELECT * FROM organizations ORDER BY id DESC LIMIT 1");
        $org = $result ? $result->fetch_assoc() : null;
    }

    if (!$org) {
        $conn->query(
            "INSERT INTO organizations (organisation_name, business_location, industry, created_at)
             VALUES ('demo', 'India', 'Technology', NOW())"
        );
        $result = $conn->query("SELECT * FROM organizations ORDER BY id DESC LIMIT 1");
        $org = $result ? $result->fetch_assoc() : null;
    }

    if (!$org) {
        return [
            'id' => 0,
            'org_id' => '60066881652',
            'organisation_name' => 'demo',
            'business_location' => 'India',
            'industry' => 'Technology',
            'address_line1' => '',
            'address_line2' => '',
            'state' => '',
            'city' => '',
            'pincode' => '',
        ];
    }

    $_SESSION['org_id'] = $org['id'];
    $_SESSION['org_name'] = $org['organisation_name'] ?? 'demo';

    return $org;
}

function settings_get_org_settings(mysqli $conn, int $organizationId): array
{
    $defaults = [
        'date_format' => 'dd/MM/yyyy',
        'field_separator' => '/',
        'primary_contact_name' => $_SESSION['username'] ?? '',
        'primary_contact_email' => payroll_current_user_email(),
        'sender_email' => 'message-service@mail.zohopayroll.in',
        'logo_path' => null,
    ];

    $stmt = $conn->prepare("SELECT * FROM organization_settings WHERE organization_id = ? LIMIT 1");
    if (!$stmt) {
        return $defaults;
    }

    $stmt->bind_param('i', $organizationId);
    $stmt->execute();
    $result = $stmt->get_result();
    $row = $result ? $result->fetch_assoc() : null;
    $stmt->close();

    return $row ? array_merge($defaults, $row) : $defaults;
}

function settings_upsert_org_settings(mysqli $conn, int $organizationId, array $data): bool
{
    $stmt = $conn->prepare(
        "INSERT INTO organization_settings
        (organization_id, date_format, field_separator, primary_contact_name, primary_contact_email, sender_email, logo_path)
        VALUES (?, ?, ?, ?, ?, ?, ?)
        ON DUPLICATE KEY UPDATE
        date_format = VALUES(date_format),
        field_separator = VALUES(field_separator),
        primary_contact_name = VALUES(primary_contact_name),
        primary_contact_email = VALUES(primary_contact_email),
        sender_email = VALUES(sender_email),
        logo_path = VALUES(logo_path)"
    );

    if (!$stmt) {
        return false;
    }

    $stmt->bind_param(
        'issssss',
        $organizationId,
        $data['date_format'],
        $data['field_separator'],
        $data['primary_contact_name'],
        $data['primary_contact_email'],
        $data['sender_email'],
        $data['logo_path']
    );
    $ok = $stmt->execute();
    $stmt->close();

    return $ok;
}

function settings_get_setup_step_state(mysqli $conn, int $organizationId, string $stepKey): bool
{
    $stmt = $conn->prepare(
        "SELECT is_completed
         FROM setup_checklist_states
         WHERE organization_id = ? AND step_key = ?
         LIMIT 1"
    );
    if (!$stmt) {
        return false;
    }

    $stmt->bind_param('is', $organizationId, $stepKey);
    $stmt->execute();
    $result = $stmt->get_result();
    $row = $result ? $result->fetch_assoc() : null;
    $stmt->close();

    return !empty($row['is_completed']);
}

function settings_set_setup_step_state(mysqli $conn, int $organizationId, string $stepKey, bool $isCompleted): bool
{
    $stmt = $conn->prepare(
        "INSERT INTO setup_checklist_states (organization_id, step_key, is_completed)
         VALUES (?, ?, ?)
         ON DUPLICATE KEY UPDATE
         is_completed = VALUES(is_completed)"
    );
    if (!$stmt) {
        return false;
    }

    $completed = $isCompleted ? 1 : 0;
    $stmt->bind_param('isi', $organizationId, $stepKey, $completed);
    $ok = $stmt->execute();
    $stmt->close();

    return $ok;
}

function settings_upsert_branding(mysqli $conn, int $organizationId, string $appearance, string $accentColor): bool
{
    $stmt = $conn->prepare(
        "INSERT INTO branding_preferences (organization_id, appearance, accent_color)
         VALUES (?, ?, ?)
         ON DUPLICATE KEY UPDATE
         appearance = VALUES(appearance),
         accent_color = VALUES(accent_color)"
    );

    if (!$stmt) {
        return false;
    }

    $stmt->bind_param('iss', $organizationId, $appearance, $accentColor);
    $ok = $stmt->execute();
    $stmt->close();

    return $ok;
}

function settings_get_salary_template(mysqli $conn, int $organizationId, string $templateCode = 'standard'): array
{
    $defaults = [
        'template_code' => $templateCode,
        'template_name' => 'Standard Salary Template',
        'annual_ctc' => '0.00',
        'basic_percent' => '50.00',
        'hra_enabled' => 0,
        'hra_percent' => '50.00',
        'conveyance_enabled' => 0,
        'conveyance_monthly' => '0.00',
        'is_default' => 1,
        'status' => 'Draft',
    ];

    $stmt = $conn->prepare("SELECT * FROM salary_templates WHERE organization_id = ? AND template_code = ? LIMIT 1");
    if (!$stmt) {
        return $defaults;
    }

    $stmt->bind_param('is', $organizationId, $templateCode);
    $stmt->execute();
    $result = $stmt->get_result();
    $row = $result ? $result->fetch_assoc() : null;
    $stmt->close();

    if ($row) {
        return array_merge($defaults, $row);
    }

    $seed = $defaults;
    $seed['template_code'] = $templateCode;
    $seed['annual_ctc'] = (float) $seed['annual_ctc'];
    $seed['basic_percent'] = (float) $seed['basic_percent'];
    $seed['hra_enabled'] = (int) $seed['hra_enabled'];
    $seed['hra_percent'] = (float) $seed['hra_percent'];
    $seed['conveyance_enabled'] = (int) $seed['conveyance_enabled'];
    $seed['conveyance_monthly'] = (float) $seed['conveyance_monthly'];
    settings_upsert_salary_template($conn, $organizationId, $seed);

    $stmt = $conn->prepare("SELECT * FROM salary_templates WHERE organization_id = ? AND template_code = ? LIMIT 1");
    if (!$stmt) {
        return $defaults;
    }

    $stmt->bind_param('is', $organizationId, $templateCode);
    $stmt->execute();
    $result = $stmt->get_result();
    $row = $result ? $result->fetch_assoc() : null;
    $stmt->close();

    return $row ? array_merge($defaults, $row) : $defaults;
}

function settings_upsert_salary_template(mysqli $conn, int $organizationId, array $data): bool
{
    $stmt = $conn->prepare(
        "INSERT INTO salary_templates
        (organization_id, template_code, template_name, annual_ctc, basic_percent, hra_enabled, hra_percent, conveyance_enabled, conveyance_monthly, is_default, status)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        ON DUPLICATE KEY UPDATE
        template_name = VALUES(template_name),
        annual_ctc = VALUES(annual_ctc),
        basic_percent = VALUES(basic_percent),
        hra_enabled = VALUES(hra_enabled),
        hra_percent = VALUES(hra_percent),
        conveyance_enabled = VALUES(conveyance_enabled),
        conveyance_monthly = VALUES(conveyance_monthly),
        is_default = VALUES(is_default),
        status = VALUES(status)"
    );

    if (!$stmt) {
        return false;
    }

    $stmt->bind_param(
        'issddididis',
        $organizationId,
        $data['template_code'],
        $data['template_name'],
        $data['annual_ctc'],
        $data['basic_percent'],
        $data['hra_enabled'],
        $data['hra_percent'],
        $data['conveyance_enabled'],
        $data['conveyance_monthly'],
        $data['is_default'],
        $data['status']
    );
    $ok = $stmt->execute();
    $stmt->close();

    return $ok;
}

function settings_default_salary_components(): array
{
    return [
        'earnings' => [
            ['key' => 'basic', 'kind' => 'earning', 'name' => 'Basic', 'display_label' => 'Basic', 'calculation_type' => 'Fixed; 50% of CTC', 'frequency_label' => null, 'epf' => 1, 'esi' => 1, 'active' => 1, 'order' => 10, 'extra' => []],
            ['key' => 'hra', 'kind' => 'earning', 'name' => 'House Rent Allowance', 'display_label' => 'House Rent Allowance', 'calculation_type' => 'Fixed; 50% of Basic', 'frequency_label' => null, 'epf' => 0, 'esi' => 1, 'active' => 1, 'order' => 20, 'extra' => []],
            ['key' => 'conveyance', 'kind' => 'earning', 'name' => 'Conveyance Allowance', 'display_label' => 'Conveyance Allowance', 'calculation_type' => 'Fixed; Flat Amount', 'frequency_label' => null, 'epf' => 1, 'esi' => 0, 'active' => 1, 'order' => 30, 'extra' => []],
            ['key' => 'children_education_allowance', 'kind' => 'earning', 'name' => 'Children Education Allowance', 'display_label' => 'Children Education Allowance', 'calculation_type' => 'Fixed; Flat Amount', 'frequency_label' => null, 'epf' => 1, 'esi' => 1, 'active' => 0, 'order' => 40, 'extra' => []],
            ['key' => 'transport_allowance', 'kind' => 'earning', 'name' => 'Transport Allowance', 'display_label' => 'Transport Allowance', 'calculation_type' => 'Fixed; Flat amount of 1600', 'frequency_label' => null, 'epf' => 1, 'esi' => 1, 'active' => 0, 'order' => 50, 'extra' => []],
            ['key' => 'travelling_allowance', 'kind' => 'earning', 'name' => 'Travelling Allowance', 'display_label' => 'Travelling Allowance', 'calculation_type' => 'Fixed; Flat Amount', 'frequency_label' => null, 'epf' => 1, 'esi' => 0, 'active' => 1, 'order' => 60, 'extra' => []],
            ['key' => 'fixed_allowance', 'kind' => 'earning', 'name' => 'Fixed Allowance', 'display_label' => 'Fixed Allowance', 'calculation_type' => 'Fixed; Flat Amount', 'frequency_label' => null, 'epf' => 1, 'esi' => 1, 'active' => 1, 'order' => 70, 'extra' => []],
            ['key' => 'overtime_allowance', 'kind' => 'earning', 'name' => 'Overtime Allowance', 'display_label' => 'Overtime Allowance', 'calculation_type' => 'Variable; Flat Amount', 'frequency_label' => null, 'epf' => 0, 'esi' => 1, 'active' => 0, 'order' => 80, 'extra' => []],
            ['key' => 'gratuity', 'kind' => 'earning', 'name' => 'Gratuity', 'display_label' => 'Gratuity', 'calculation_type' => 'Variable; Flat Amount', 'frequency_label' => null, 'epf' => 0, 'esi' => 0, 'active' => 1, 'order' => 90, 'extra' => []],
            ['key' => 'bonus', 'kind' => 'earning', 'name' => 'Bonus', 'display_label' => 'Bonus', 'calculation_type' => 'Variable; Flat Amount', 'frequency_label' => null, 'epf' => 0, 'esi' => 0, 'active' => 1, 'order' => 100, 'extra' => []],
            ['key' => 'commission', 'kind' => 'earning', 'name' => 'Commission', 'display_label' => 'Commission', 'calculation_type' => 'Variable; Flat Amount', 'frequency_label' => null, 'epf' => 0, 'esi' => 1, 'active' => 1, 'order' => 110, 'extra' => []],
            ['key' => 'leave_encashment', 'kind' => 'earning', 'name' => 'Leave Encashment', 'display_label' => 'Leave Encashment', 'calculation_type' => 'Variable; Flat Amount', 'frequency_label' => null, 'epf' => 0, 'esi' => 0, 'active' => 1, 'order' => 120, 'extra' => []],
            ['key' => 'notice_pay', 'kind' => 'earning', 'name' => 'Notice Pay', 'display_label' => 'Notice Pay', 'calculation_type' => 'Variable; Flat Amount', 'frequency_label' => null, 'epf' => 0, 'esi' => 0, 'active' => 1, 'order' => 130, 'extra' => []],
            ['key' => 'hold_salary', 'kind' => 'earning', 'name' => 'Hold Salary', 'display_label' => 'Hold Salary (Non Taxable)', 'calculation_type' => 'Variable; Flat Amount', 'frequency_label' => null, 'epf' => 0, 'esi' => 0, 'active' => 1, 'order' => 140, 'extra' => []],
            ['key' => 'correction_basic', 'kind' => 'correction', 'name' => 'Basic Correction', 'display_label' => 'Basic', 'calculation_type' => 'Variable; Flat Amount', 'frequency_label' => null, 'epf' => 0, 'esi' => 0, 'active' => 1, 'order' => 150, 'extra' => ['correction_for' => 'Basic']],
        ],
        'deductions' => [
            ['key' => 'withheld_salary', 'kind' => 'deduction', 'name' => 'Withheld Salary', 'display_label' => 'Withheld Salary', 'calculation_type' => null, 'frequency_label' => 'One Time', 'epf' => 0, 'esi' => 0, 'active' => 1, 'order' => 10, 'extra' => []],
            ['key' => 'notice_pay_deduction', 'kind' => 'deduction', 'name' => 'Notice Pay Deduction', 'display_label' => 'Notice Pay Deduction', 'calculation_type' => null, 'frequency_label' => 'One Time', 'epf' => 0, 'esi' => 0, 'active' => 1, 'order' => 20, 'extra' => []],
        ],
        'benefits' => [
            ['key' => 'voluntary_provident_fund', 'kind' => 'benefit', 'name' => 'Voluntary Provident Fund', 'display_label' => 'Voluntary Provident Fund', 'calculation_type' => null, 'frequency_label' => 'Recurring', 'epf' => 0, 'esi' => 0, 'active' => 0, 'order' => 10, 'extra' => []],
        ],
        'reimbursements' => [
            ['key' => 'fuel_reimbursement', 'kind' => 'reimbursement', 'name' => 'Fuel Reimbursement', 'display_label' => 'Fuel Reimbursement', 'calculation_type' => null, 'frequency_label' => null, 'epf' => 0, 'esi' => 0, 'active' => 0, 'order' => 10, 'extra' => ['max_amount' => 0]],
            ['key' => 'driver_reimbursement', 'kind' => 'reimbursement', 'name' => 'Driver Reimbursement', 'display_label' => 'Driver Reimbursement', 'calculation_type' => null, 'frequency_label' => null, 'epf' => 0, 'esi' => 0, 'active' => 0, 'order' => 20, 'extra' => ['max_amount' => 0]],
            ['key' => 'vehicle_maintenance_reimbursement', 'kind' => 'reimbursement', 'name' => 'Vehicle Maintenance Reimbursement', 'display_label' => 'Vehicle Maintenance Reimbursement', 'calculation_type' => null, 'frequency_label' => null, 'epf' => 0, 'esi' => 0, 'active' => 0, 'order' => 30, 'extra' => ['max_amount' => 0]],
            ['key' => 'telephone_reimbursement', 'kind' => 'reimbursement', 'name' => 'Telephone Reimbursement', 'display_label' => 'Telephone Reimbursement', 'calculation_type' => null, 'frequency_label' => null, 'epf' => 0, 'esi' => 0, 'active' => 0, 'order' => 40, 'extra' => ['max_amount' => 0]],
            ['key' => 'leave_travel_allowance', 'kind' => 'reimbursement', 'name' => 'Leave Travel Allowance', 'display_label' => 'Leave Travel Allowance', 'calculation_type' => null, 'frequency_label' => null, 'epf' => 0, 'esi' => 0, 'active' => 0, 'order' => 50, 'extra' => ['max_amount' => 0]],
        ],
    ];
}

function settings_ensure_salary_component_tables(mysqli $conn): void
{
    $conn->query(
        "CREATE TABLE IF NOT EXISTS salary_components (
            id INT AUTO_INCREMENT PRIMARY KEY,
            organization_id INT NOT NULL,
            component_group VARCHAR(40) NOT NULL,
            component_kind VARCHAR(40) NOT NULL,
            component_key VARCHAR(120) NOT NULL,
            name_in_payslip VARCHAR(150) NOT NULL,
            display_label VARCHAR(150) NOT NULL,
            calculation_type VARCHAR(150) DEFAULT NULL,
            frequency_label VARCHAR(150) DEFAULT NULL,
            consider_for_epf TINYINT(1) NOT NULL DEFAULT 0,
            consider_for_esi TINYINT(1) NOT NULL DEFAULT 0,
            is_active TINYINT(1) NOT NULL DEFAULT 1,
            max_amount DECIMAL(14,2) DEFAULT NULL,
            extra_json LONGTEXT DEFAULT NULL,
            display_order INT NOT NULL DEFAULT 0,
            is_default TINYINT(1) NOT NULL DEFAULT 0,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            UNIQUE KEY uniq_org_salary_component (organization_id, component_group, component_key),
            KEY idx_salary_component_group (organization_id, component_group),
            KEY idx_salary_component_kind (component_kind)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"
    );
}

function settings_seed_salary_components(mysqli $conn, int $organizationId): void
{
    if ($organizationId <= 0) {
        return;
    }

    $check = $conn->prepare("SELECT id FROM salary_components WHERE organization_id = ? LIMIT 1");
    if (!$check) {
        return;
    }
    $check->bind_param('i', $organizationId);
    $check->execute();
    $exists = $check->get_result();
    $hasRows = $exists && $exists->fetch_assoc();
    $check->close();

    if ($hasRows) {
        return;
    }

    $stmt = $conn->prepare(
        "INSERT INTO salary_components
        (organization_id, component_group, component_kind, component_key, name_in_payslip, display_label, calculation_type, frequency_label, consider_for_epf, consider_for_esi, is_active, max_amount, extra_json, display_order, is_default)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
    );
    if (!$stmt) {
        return;
    }

    foreach (settings_default_salary_components() as $group => $rows) {
        foreach ($rows as $row) {
            $extraJson = json_encode($row['extra'] ?? [], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
            $maxAmount = $row['extra']['max_amount'] ?? null;
            if ($maxAmount === 0 || $maxAmount === '0') {
                $maxAmount = 0.00;
            } elseif ($maxAmount === null) {
                $maxAmount = null;
            }
            $componentGroup = (string) $group;
            $componentKind = (string) $row['kind'];
            $componentKey = (string) $row['key'];
            $name = (string) $row['name'];
            $displayLabel = (string) $row['display_label'];
            $calculationType = $row['calculation_type'];
            $frequencyLabel = $row['frequency_label'];
            $considerForEpf = (int) ($row['epf'] ?? 0);
            $considerForEsi = (int) ($row['esi'] ?? 0);
            $isActive = (int) ($row['active'] ?? 1);
            $displayOrder = (int) ($row['order'] ?? 0);
            $isDefault = 1;

            $stmt->bind_param(
                'isssssssiiidsii',
                $organizationId,
                $componentGroup,
                $componentKind,
                $componentKey,
                $name,
                $displayLabel,
                $calculationType,
                $frequencyLabel,
                $considerForEpf,
                $considerForEsi,
                $isActive,
                $maxAmount,
                $extraJson,
                $displayOrder,
                $isDefault
            );
            $stmt->execute();
        }
    }

    $stmt->close();
}

function settings_get_salary_components(mysqli $conn, int $organizationId, string $componentGroup): array
{
    settings_ensure_salary_component_tables($conn);
    settings_seed_salary_components($conn, $organizationId);

    $rows = [];
    $stmt = $conn->prepare(
        "SELECT *
         FROM salary_components
         WHERE organization_id = ? AND component_group = ?
         ORDER BY display_order ASC, is_default DESC, name_in_payslip ASC, id ASC"
    );
    if (!$stmt) {
        return $rows;
    }

    $stmt->bind_param('is', $organizationId, $componentGroup);
    $stmt->execute();
    $result = $stmt->get_result();
    while ($result && ($row = $result->fetch_assoc())) {
        $row['extra'] = json_decode((string) ($row['extra_json'] ?? '[]'), true) ?: [];
        $rows[] = $row;
    }
    $stmt->close();

    return $rows;
}

function settings_get_salary_component_by_id(mysqli $conn, int $organizationId, int $componentId): ?array
{
    settings_ensure_salary_component_tables($conn);

    $stmt = $conn->prepare(
        "SELECT *
         FROM salary_components
         WHERE organization_id = ? AND id = ?
         LIMIT 1"
    );
    if (!$stmt) {
        return null;
    }

    $stmt->bind_param('ii', $organizationId, $componentId);
    $stmt->execute();
    $result = $stmt->get_result();
    $row = $result ? $result->fetch_assoc() : null;
    $stmt->close();

    if ($row) {
        $row['extra'] = json_decode((string) ($row['extra_json'] ?? '[]'), true) ?: [];
    }

    return $row ?: null;
}

function settings_save_salary_component(mysqli $conn, int $organizationId, array $data): bool
{
    settings_ensure_salary_component_tables($conn);

    $stmt = $conn->prepare(
        "INSERT INTO salary_components
        (organization_id, component_group, component_kind, component_key, name_in_payslip, display_label, calculation_type, frequency_label, consider_for_epf, consider_for_esi, is_active, max_amount, extra_json, display_order, is_default)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        ON DUPLICATE KEY UPDATE
        name_in_payslip = VALUES(name_in_payslip),
        display_label = VALUES(display_label),
        calculation_type = VALUES(calculation_type),
        frequency_label = VALUES(frequency_label),
        consider_for_epf = VALUES(consider_for_epf),
        consider_for_esi = VALUES(consider_for_esi),
        is_active = VALUES(is_active),
        max_amount = VALUES(max_amount),
        extra_json = VALUES(extra_json),
        display_order = VALUES(display_order)"
    );
    if (!$stmt) {
        return false;
    }

    $componentGroup = (string) ($data['component_group'] ?? 'earnings');
    $componentKind = (string) ($data['component_kind'] ?? 'earning');
    $componentKey = trim((string) ($data['component_key'] ?? ''));
    $nameInPayslip = trim((string) ($data['name_in_payslip'] ?? ''));
    $displayLabel = trim((string) ($data['display_label'] ?? $nameInPayslip));
    $calculationType = trim((string) ($data['calculation_type'] ?? ''));
    $frequencyLabel = trim((string) ($data['frequency_label'] ?? ''));
    $considerForEpf = !empty($data['consider_for_epf']) ? 1 : 0;
    $considerForEsi = !empty($data['consider_for_esi']) ? 1 : 0;
    $isActive = !empty($data['is_active']) ? 1 : 0;
    $maxAmount = isset($data['max_amount']) && $data['max_amount'] !== '' ? (float) $data['max_amount'] : null;
    $extraJson = json_encode($data['extra_json'] ?? [], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    $displayOrder = max(0, (int) ($data['display_order'] ?? 999));
    $isDefault = !empty($data['is_default']) ? 1 : 0;

    $stmt->bind_param(
        'isssssssiiidsii',
        $organizationId,
        $componentGroup,
        $componentKind,
        $componentKey,
        $nameInPayslip,
        $displayLabel,
        $calculationType,
        $frequencyLabel,
        $considerForEpf,
        $considerForEsi,
        $isActive,
        $maxAmount,
        $extraJson,
        $displayOrder,
        $isDefault
    );
    $ok = $stmt->execute();
    $stmt->close();

    return $ok;
}

function settings_update_salary_component_status(mysqli $conn, int $organizationId, int $componentId, bool $isActive): bool
{
    settings_ensure_salary_component_tables($conn);

    $stmt = $conn->prepare(
        "UPDATE salary_components
         SET is_active = ?
         WHERE organization_id = ? AND id = ?"
    );
    if (!$stmt) {
        return false;
    }

    $active = $isActive ? 1 : 0;
    $stmt->bind_param('iii', $active, $organizationId, $componentId);
    $ok = $stmt->execute();
    $stmt->close();

    return $ok;
}

function settings_delete_salary_component(mysqli $conn, int $organizationId, int $componentId): bool
{
    settings_ensure_salary_component_tables($conn);

    $stmt = $conn->prepare(
        "DELETE FROM salary_components
         WHERE organization_id = ? AND id = ?"
    );
    if (!$stmt) {
        return false;
    }

    $stmt->bind_param('ii', $organizationId, $componentId);
    $ok = $stmt->execute();
    $stmt->close();

    return $ok;
}

function settings_ensure_employee_organization_column(mysqli $conn, int $organizationId): void
{
    if ($organizationId <= 0) {
        return;
    }

    $hasOrgColumn = false;
    $columnResult = $conn->query("SHOW COLUMNS FROM employees LIKE 'organization_id'");
    if ($columnResult && $columnResult->num_rows > 0) {
        $hasOrgColumn = true;
    }

    if (!$hasOrgColumn) {
        $conn->query("ALTER TABLE employees ADD COLUMN organization_id INT NULL AFTER id");
    }

    $conn->query("UPDATE employees SET organization_id = {$organizationId} WHERE organization_id IS NULL OR organization_id = 0");

    $indexExists = false;
    $indexResult = $conn->query("SHOW INDEX FROM employees WHERE Key_name = 'idx_employees_organization_id'");
    if ($indexResult && $indexResult->num_rows > 0) {
        $indexExists = true;
    }

    if (!$indexExists) {
        $conn->query("ALTER TABLE employees ADD INDEX idx_employees_organization_id (organization_id)");
    }
}

function settings_ensure_audit_tables(mysqli $conn): void
{
    $queries = [
        "CREATE TABLE IF NOT EXISTS employee_change_history (
            id INT AUTO_INCREMENT PRIMARY KEY,
            organization_id INT NOT NULL,
            employee_id INT NOT NULL,
            tab_key VARCHAR(60) NOT NULL DEFAULT 'basic',
            action_type VARCHAR(40) NOT NULL DEFAULT 'updated',
            changed_by VARCHAR(190) DEFAULT NULL,
            old_values_json LONGTEXT DEFAULT NULL,
            new_values_json LONGTEXT DEFAULT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            KEY idx_employee_change_org (organization_id),
            KEY idx_employee_change_employee (employee_id),
            KEY idx_employee_change_tab (tab_key)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
        "CREATE TABLE IF NOT EXISTS loan_change_history (
            id INT AUTO_INCREMENT PRIMARY KEY,
            org_id VARCHAR(50) NOT NULL,
            loan_id INT NOT NULL,
            employee_id INT DEFAULT NULL,
            action_type VARCHAR(40) NOT NULL DEFAULT 'updated',
            changed_by VARCHAR(190) DEFAULT NULL,
            old_values_json LONGTEXT DEFAULT NULL,
            new_values_json LONGTEXT DEFAULT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            KEY idx_loan_change_org (org_id),
            KEY idx_loan_change_loan (loan_id),
            KEY idx_loan_change_employee (employee_id)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
        "CREATE TABLE IF NOT EXISTS approval_requests (
            id INT AUTO_INCREMENT PRIMARY KEY,
            org_id VARCHAR(50) NOT NULL,
            module_name VARCHAR(80) NOT NULL,
            reference_type VARCHAR(80) NOT NULL,
            reference_id INT NOT NULL,
            employee_id INT DEFAULT NULL,
            approval_status ENUM('pending','approved','rejected') NOT NULL DEFAULT 'pending',
            requested_by VARCHAR(190) DEFAULT NULL,
            approved_by VARCHAR(190) DEFAULT NULL,
            approval_notes TEXT DEFAULT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            KEY idx_approval_org (org_id),
            KEY idx_approval_module (module_name),
            KEY idx_approval_reference (reference_type, reference_id),
            KEY idx_approval_status (approval_status)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
        "CREATE TABLE IF NOT EXISTS salary_template_change_history (
            id INT AUTO_INCREMENT PRIMARY KEY,
            organization_id INT NOT NULL,
            template_code VARCHAR(60) NOT NULL,
            action_type VARCHAR(40) NOT NULL DEFAULT 'updated',
            changed_by VARCHAR(190) DEFAULT NULL,
            old_values_json LONGTEXT DEFAULT NULL,
            new_values_json LONGTEXT DEFAULT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            KEY idx_salary_template_change_org (organization_id),
            KEY idx_salary_template_change_code (template_code)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
        "CREATE TABLE IF NOT EXISTS employee_salary_holds (
            id INT AUTO_INCREMENT PRIMARY KEY,
            organization_id INT NOT NULL,
            employee_id INT NOT NULL,
            salary_month_from VARCHAR(20) NOT NULL,
            salary_month_to VARCHAR(20) NOT NULL,
            release_month VARCHAR(20) DEFAULT NULL,
            hold_status ENUM('pending','held','released','approved') NOT NULL DEFAULT 'pending',
            approval_status ENUM('pending','approved','rejected') NOT NULL DEFAULT 'pending',
            hold_reason TEXT DEFAULT NULL,
            created_by VARCHAR(190) DEFAULT NULL,
            approved_by VARCHAR(190) DEFAULT NULL,
            approved_at DATETIME DEFAULT NULL,
            released_by VARCHAR(190) DEFAULT NULL,
            released_at DATETIME DEFAULT NULL,
            notes TEXT DEFAULT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            KEY idx_salary_hold_org (organization_id),
            KEY idx_salary_hold_employee (employee_id),
            KEY idx_salary_hold_status (hold_status),
            KEY idx_salary_hold_approval (approval_status)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
    ];

    foreach ($queries as $query) {
        $conn->query($query);
    }
}

function settings_ensure_loan_approval_columns(mysqli $conn): void
{
    $existing = [];
    $result = $conn->query("SHOW COLUMNS FROM employee_loans");
    if ($result) {
        while ($row = $result->fetch_assoc()) {
            $existing[] = $row['Field'];
        }
    }

    $needed = [
        'approval_status' => "ALTER TABLE employee_loans ADD COLUMN approval_status ENUM('pending','approved','rejected') NOT NULL DEFAULT 'pending' AFTER status",
        'approved_by' => "ALTER TABLE employee_loans ADD COLUMN approved_by VARCHAR(190) DEFAULT NULL AFTER approval_status",
        'approved_at' => "ALTER TABLE employee_loans ADD COLUMN approved_at DATETIME DEFAULT NULL AFTER approved_by",
        'approval_notes' => "ALTER TABLE employee_loans ADD COLUMN approval_notes TEXT DEFAULT NULL AFTER approved_at",
    ];

    foreach ($needed as $column => $sql) {
        if (!in_array($column, $existing, true)) {
            $conn->query($sql);
        }
    }
}

function settings_log_employee_history(
    mysqli $conn,
    int $organizationId,
    int $employeeId,
    string $tabKey,
    string $actionType,
    ?string $changedBy,
    ?array $oldValues,
    ?array $newValues
): void {
    settings_ensure_audit_tables($conn);

    $stmt = $conn->prepare(
        "INSERT INTO employee_change_history
        (organization_id, employee_id, tab_key, action_type, changed_by, old_values_json, new_values_json)
        VALUES (?, ?, ?, ?, ?, ?, ?)"
    );

    if (!$stmt) {
        return;
    }

    $oldJson = $oldValues !== null ? json_encode($oldValues, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : null;
    $newJson = $newValues !== null ? json_encode($newValues, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : null;
    $stmt->bind_param('iisssss', $organizationId, $employeeId, $tabKey, $actionType, $changedBy, $oldJson, $newJson);
    $stmt->execute();
    $stmt->close();
}

function settings_log_loan_history(
    mysqli $conn,
    string $orgId,
    int $loanId,
    ?int $employeeId,
    string $actionType,
    ?string $changedBy,
    ?array $oldValues,
    ?array $newValues
): void {
    settings_ensure_audit_tables($conn);

    $stmt = $conn->prepare(
        "INSERT INTO loan_change_history
        (org_id, loan_id, employee_id, action_type, changed_by, old_values_json, new_values_json)
        VALUES (?, ?, ?, ?, ?, ?, ?)"
    );

    if (!$stmt) {
        return;
    }

    $oldJson = $oldValues !== null ? json_encode($oldValues, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : null;
    $newJson = $newValues !== null ? json_encode($newValues, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : null;
    $stmt->bind_param('siissss', $orgId, $loanId, $employeeId, $actionType, $changedBy, $oldJson, $newJson);
    $stmt->execute();
    $stmt->close();
}

function settings_create_approval_request(
    mysqli $conn,
    string $orgId,
    string $moduleName,
    string $referenceType,
    int $referenceId,
    ?int $employeeId,
    ?string $requestedBy,
    string $approvalStatus = 'pending',
    ?string $approvalNotes = null,
    ?string $approvedBy = null
): void {
    settings_ensure_audit_tables($conn);

    $stmt = $conn->prepare(
        "INSERT INTO approval_requests
        (org_id, module_name, reference_type, reference_id, employee_id, approval_status, requested_by, approved_by, approval_notes)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"
    );

    if (!$stmt) {
        return;
    }

    $stmt->bind_param(
        'sssisssss',
        $orgId,
        $moduleName,
        $referenceType,
        $referenceId,
        $employeeId,
        $approvalStatus,
        $requestedBy,
        $approvedBy,
        $approvalNotes
    );
    $stmt->execute();
    $stmt->close();
}

function settings_log_salary_template_history(
    mysqli $conn,
    int $organizationId,
    string $templateCode,
    string $actionType,
    ?string $changedBy,
    ?array $oldValues,
    ?array $newValues
): void {
    settings_ensure_audit_tables($conn);

    $stmt = $conn->prepare(
        "INSERT INTO salary_template_change_history
        (organization_id, template_code, action_type, changed_by, old_values_json, new_values_json)
        VALUES (?, ?, ?, ?, ?, ?)"
    );

    if (!$stmt) {
        return;
    }

    $oldJson = $oldValues !== null ? json_encode($oldValues, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : null;
    $newJson = $newValues !== null ? json_encode($newValues, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : null;
    $stmt->bind_param('isssss', $organizationId, $templateCode, $actionType, $changedBy, $oldJson, $newJson);
    $stmt->execute();
    $stmt->close();
}

function settings_ensure_salary_hold_tables(mysqli $conn): void
{
    settings_ensure_audit_tables($conn);
}

function settings_salary_hold_month_label(?string $monthKey): string
{
    $monthKey = trim((string) $monthKey);
    if ($monthKey === '') {
        return '-';
    }

    $date = DateTime::createFromFormat('Y-m', $monthKey);
    if ($date instanceof DateTime) {
        return $date->format('F Y');
    }

    $parsed = strtotime($monthKey);
    if ($parsed !== false) {
        return date('F Y', $parsed);
    }

    return $monthKey;
}

function settings_salary_hold_pending_months(array $row): int
{
    $fromKey = trim((string) ($row['salary_month_from'] ?? ''));
    $toKey = trim((string) ($row['salary_month_to'] ?? ''));
    if ($fromKey === '' || $toKey === '') {
        return 0;
    }

    $from = DateTime::createFromFormat('Y-m', $fromKey);
    $to = DateTime::createFromFormat('Y-m', $toKey);
    if (!$from || !$to) {
        return 0;
    }

    $months = ((int) $to->format('Y') - (int) $from->format('Y')) * 12 + ((int) $to->format('n') - (int) $from->format('n'));
    return max(0, $months);
}

function settings_get_salary_hold_notifications(mysqli $conn, int $organizationId): array
{
    $rows = [];
    $stmt = $conn->prepare(
        "SELECT h.id,
                h.organization_id,
                h.employee_id AS hold_employee_id,
                h.salary_month_from,
                h.salary_month_to,
                h.release_month,
                h.hold_status,
                h.approval_status,
                h.hold_reason,
                h.created_by,
                h.approved_by,
                h.approved_at,
                h.released_by,
                h.released_at,
                h.notes,
                h.created_at,
                h.updated_at,
                e.employee_code,
                e.full_name,
                e.first_name,
                e.middle_name,
                e.last_name,
                e.date_of_joining
         FROM employee_salary_holds h
         LEFT JOIN employees e ON e.id = h.employee_id
         WHERE h.organization_id = ?
         ORDER BY h.updated_at DESC, h.id DESC"
    );

    if (!$stmt) {
        return $rows;
    }

    $stmt->bind_param('i', $organizationId);
    $stmt->execute();
    $result = $stmt->get_result();
    while ($result && ($row = $result->fetch_assoc())) {
        $rows[] = $row;
    }
    $stmt->close();

    return $rows;
}

function settings_salary_hold_notification_details(array $hold): array
{
    $fullName = trim((string) ($hold['full_name'] ?? ''));
    if ($fullName === '') {
        $first = trim((string) ($hold['first_name'] ?? ''));
        $middle = trim((string) ($hold['middle_name'] ?? ''));
        $last = trim((string) ($hold['last_name'] ?? ''));
        $fullName = trim($first . ' ' . $middle . ' ' . $last);
    }
    if ($fullName === '') {
        $fullName = 'Employee';
    }

    $employeeCode = trim((string) ($hold['employee_code'] ?? ($hold['employee_number'] ?? '')));
    $employeeLabel = $employeeCode !== '' ? $fullName . ' (' . $employeeCode . ')' : $fullName;
    $approvalStatus = strtolower(trim((string) ($hold['approval_status'] ?? 'pending')));
    $holdStatus = strtolower(trim((string) ($hold['hold_status'] ?? 'pending')));
    $pendingMonths = settings_salary_hold_pending_months($hold);
    $periodLabel = settings_salary_hold_month_label((string) ($hold['salary_month_from'] ?? '')) . ' to ' . settings_salary_hold_month_label((string) ($hold['salary_month_to'] ?? ''));
    $releaseLabel = settings_salary_hold_month_label((string) ($hold['release_month'] ?? ''));

    if ($holdStatus === 'released') {
        $statusLabel = 'Released';
        $message = 'Salary release is scheduled for ' . $employeeLabel . '.';
        $icon = 'fa-solid fa-circle-check';
    } elseif ($holdStatus === 'approved') {
        $statusLabel = 'Approved';
        $message = 'Salary hold is approved for ' . $employeeLabel . '.';
        $icon = 'fa-solid fa-circle-check';
    } elseif ($holdStatus === 'held') {
        $statusLabel = 'Held';
        $message = $approvalStatus === 'pending'
            ? 'Salary hold approval is pending for ' . $employeeLabel . '.'
            : 'Salary is currently on hold for ' . $employeeLabel . '.';
        $icon = 'fa-solid fa-triangle-exclamation';
    } else {
        $statusLabel = 'Pending';
        $message = 'Salary hold is pending for ' . $employeeLabel . '.';
        $icon = 'fa-solid fa-hourglass-half';
    }

    return [
        'employee_label' => $employeeLabel,
        'approval_status' => $approvalStatus,
        'hold_status' => $holdStatus,
        'status_label' => $statusLabel,
        'title' => 'Salary Hold',
        'message' => $message,
        'period_label' => $periodLabel,
        'release_label' => $releaseLabel,
        'pending_months' => $pendingMonths,
        'icon' => $icon,
        'status_key' => $holdStatus !== '' ? $holdStatus : $approvalStatus,
    ];
}

function settings_normalize_payrun_status(?string $status): array
{
    $status = strtolower(trim((string) $status));
    if ($status === '') {
        $status = 'draft';
    }

    $pendingStatuses = ['draft', 'pending', 'queued', 'processing', 'in_progress'];
    $paidStatuses = ['paid', 'released', 'approved', 'completed', 'processed', 'success'];

    if (in_array($status, $paidStatuses, true)) {
        return [
            'key' => 'paid',
            'label' => 'Paid',
        ];
    }

    if (in_array($status, $pendingStatuses, true)) {
        return [
            'key' => 'pending',
            'label' => 'Pending',
        ];
    }

    return [
        'key' => $status,
        'label' => ucfirst($status),
    ];
}

function settings_get_payrun_notifications(mysqli $conn, int $organizationId, string $orgName): array
{
    $rows = [];
    $safeOrgName = trim($orgName) !== '' ? $orgName : 'demo';
    $stmt = $conn->prepare(
        "SELECT id, org_id, pay_type, component, pay_date, status
         FROM payruns
         WHERE org_id = ? OR org_id = ?
         ORDER BY id DESC"
    );

    if (!$stmt) {
        return $rows;
    }

    $orgIdString = (string) $organizationId;
    $stmt->bind_param('ss', $safeOrgName, $orgIdString);
    $stmt->execute();
    $result = $stmt->get_result();

    while ($result && ($row = $result->fetch_assoc())) {
        $statusInfo = settings_normalize_payrun_status((string) ($row['status'] ?? ''));
        $payDate = trim((string) ($row['pay_date'] ?? ''));
        $payDateLabel = '-';
        if ($payDate !== '') {
            $dt = DateTime::createFromFormat('Y-m-d', $payDate);
            $payDateLabel = $dt instanceof DateTime ? $dt->format('d M Y') : $payDate;
        }

        $payType = (string) ($row['pay_type'] ?? '');
        $component = trim((string) ($row['component'] ?? ''));
        $description = $statusInfo['key'] === 'paid'
            ? 'Payroll has been paid for ' . $payDateLabel . '.'
            : 'Payroll is pending for ' . $payDateLabel . '.';
        if ($payType === 'one_time' && $component !== '') {
            $description = ($statusInfo['key'] === 'paid'
                ? 'One time payout for ' . $component . ' has been paid.'
                : 'One time payout for ' . $component . ' is pending.');
        } elseif ($payType === 'off_cycle') {
            $description = $statusInfo['key'] === 'paid'
                ? 'Off cycle payrun has been paid.'
                : 'Off cycle payrun is pending.';
        }

        $rows[] = [
            'id' => $row['id'] ?? null,
            'pay_type' => $payType,
            'component' => $component,
            'pay_date' => $payDate,
            'pay_date_label' => $payDateLabel,
            'status' => $row['status'] ?? 'draft',
            'status_key' => $statusInfo['key'],
            'status_label' => $statusInfo['label'],
            'title' => $statusInfo['key'] === 'paid' ? 'Payroll Paid' : 'Payroll Pending',
            'message' => $description,
            'icon' => $statusInfo['key'] === 'paid' ? 'fa-solid fa-circle-check' : 'fa-solid fa-hourglass-half',
        ];
    }

    $stmt->close();

    return $rows;
}

function settings_save_salary_hold_entry(
    mysqli $conn,
    int $organizationId,
    int $employeeId,
    string $salaryMonthFrom,
    string $salaryMonthTo,
    ?string $releaseMonth,
    string $holdStatus,
    string $approvalStatus,
    ?string $holdReason,
    ?string $createdBy,
    ?string $notes = null,
    ?string $approvedBy = null,
    ?string $releasedBy = null,
    ?string $approvedAt = null,
    ?string $releasedAt = null
): bool {
    settings_ensure_salary_hold_tables($conn);

    $stmt = $conn->prepare(
        "INSERT INTO employee_salary_holds
        (organization_id, employee_id, salary_month_from, salary_month_to, release_month, hold_status, approval_status, hold_reason, created_by, approved_by, approved_at, released_by, released_at, notes)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
    );

    if (!$stmt) {
        return false;
    }

    $stmt->bind_param(
        'iissssssssssss',
        $organizationId,
        $employeeId,
        $salaryMonthFrom,
        $salaryMonthTo,
        $releaseMonth,
        $holdStatus,
        $approvalStatus,
        $holdReason,
        $createdBy,
        $approvedBy,
        $approvedAt,
        $releasedBy,
        $releasedAt,
        $notes
    );

    $ok = $stmt->execute();
    $stmt->close();

    return $ok;
}

function settings_get_branding(mysqli $conn, int $organizationId): array
{
    $defaults = [
        'appearance' => 'dark',
        'accent_color' => 'blue',
    ];

    $stmt = $conn->prepare("SELECT * FROM branding_preferences WHERE organization_id = ? LIMIT 1");
    if (!$stmt) {
        return $defaults;
    }

    $stmt->bind_param('i', $organizationId);
    $stmt->execute();
    $result = $stmt->get_result();
    $row = $result ? $result->fetch_assoc() : null;
    $stmt->close();

    return $row ? array_merge($defaults, $row) : $defaults;
}

function settings_ensure_head_office(mysqli $conn, array $org): void
{
    if (empty($org['id'])) {
        return;
    }

    $stmt = $conn->prepare("SELECT id FROM work_locations WHERE organization_id = ? AND is_filing_address = 1 LIMIT 1");
    if (!$stmt) {
        return;
    }
    $stmt->bind_param('i', $org['id']);
    $stmt->execute();
    $result = $stmt->get_result();
    $exists = $result && $result->fetch_assoc();
    $stmt->close();

    if ($exists) {
        return;
    }

    $locationName = 'Head Office';
    $address1 = $org['address_line1'] ?? '';
    $address2 = $org['address_line2'] ?? '';
    $state = $org['state'] ?? '';
    $city = $org['city'] ?? '';
    $pincode = $org['pincode'] ?? '';

    $insert = $conn->prepare(
        "INSERT INTO work_locations
         (organization_id, location_name, address_line1, address_line2, state, city, pincode, is_filing_address)
         VALUES (?, ?, ?, ?, ?, ?, ?, 1)"
    );
    if ($insert) {
        $insert->bind_param('issssss', $org['id'], $locationName, $address1, $address2, $state, $city, $pincode);
        $insert->execute();
        $insert->close();
    }
}

function settings_get_states_for_country(mysqli $conn, string $country = 'India'): array
{
    return get_locations($conn, $country);
}

function settings_default_roles(): array
{
    return [
        [
            'name' => 'Admin',
            'description' => 'Unrestricted access to all modules.',
            'is_system' => 1,
            'permissions' => ['all_access' => true],
        ],
        [
            'name' => 'Manager',
            'description' => 'Access to all modules except organisation settings.',
            'is_system' => 1,
            'permissions' => ['manage_modules' => true, 'organisation_settings' => false],
        ],
        [
            'name' => 'Reimbursements and POI Reviewer',
            'description' => 'View and approve reimbursements and proof of investments.',
            'is_system' => 1,
            'permissions' => ['reimbursements' => true, 'poi' => true, 'approvals' => true],
        ],
    ];
}

function settings_seed_roles(mysqli $conn, int $organizationId): void
{
    $check = $conn->prepare("SELECT id FROM settings_roles WHERE organization_id = ? LIMIT 1");
    if (!$check) {
        return;
    }
    $check->bind_param('i', $organizationId);
    $check->execute();
    $exists = $check->get_result();
    $hasRows = $exists && $exists->fetch_assoc();
    $check->close();

    if ($hasRows) {
        return;
    }

    $stmt = $conn->prepare(
        "INSERT INTO settings_roles (organization_id, role_name, description, is_system, permissions_json)
         VALUES (?, ?, ?, ?, ?)"
    );
    if (!$stmt) {
        return;
    }

    foreach (settings_default_roles() as $role) {
        $permissions = json_encode($role['permissions'], JSON_UNESCAPED_UNICODE);
        $stmt->bind_param(
            'issis',
            $organizationId,
            $role['name'],
            $role['description'],
            $role['is_system'],
            $permissions
        );
        $stmt->execute();
    }

    $stmt->close();
}

function settings_get_roles(mysqli $conn, int $organizationId): array
{
    settings_seed_roles($conn, $organizationId);

    $roles = [];
    $stmt = $conn->prepare(
        "SELECT * FROM settings_roles
         WHERE organization_id = ?
         ORDER BY is_system DESC, role_name ASC"
    );
    if (!$stmt) {
        return $roles;
    }

    $stmt->bind_param('i', $organizationId);
    $stmt->execute();
    $result = $stmt->get_result();
    while ($result && ($row = $result->fetch_assoc())) {
        $roles[] = $row;
    }
    $stmt->close();

    return $roles;
}

function settings_add_role(mysqli $conn, int $organizationId, string $name, string $description, array $permissions): bool
{
    $permissionsJson = json_encode($permissions, JSON_UNESCAPED_UNICODE);
    $stmt = $conn->prepare(
        "INSERT INTO settings_roles (organization_id, role_name, description, is_system, permissions_json)
         VALUES (?, ?, ?, 0, ?)"
    );
    if (!$stmt) {
        return false;
    }

    $stmt->bind_param('isss', $organizationId, $name, $description, $permissionsJson);
    $ok = $stmt->execute();
    $stmt->close();

    return $ok;
}

function settings_seed_users(mysqli $conn, int $organizationId): void
{
    $check = $conn->prepare("SELECT id FROM settings_users WHERE organization_id = ? LIMIT 1");
    if (!$check) {
        return;
    }
    $check->bind_param('i', $organizationId);
    $check->execute();
    $exists = $check->get_result();
    $hasRows = $exists && $exists->fetch_assoc();
    $check->close();

    if ($hasRows) {
        return;
    }

    settings_seed_roles($conn, $organizationId);

    $name = trim((string) ($_SESSION['username'] ?? $_SESSION['org_name'] ?? 'Admin User'));
    $email = payroll_current_user_email('admin@example.com');
    $stmt = $conn->prepare(
        "INSERT INTO settings_users (organization_id, full_name, email, role_name, status, avatar_path)
         VALUES (?, ?, ?, 'Admin', 'ACTIVE', NULL)"
    );
    if (!$stmt) {
        return;
    }

    $stmt->bind_param('iss', $organizationId, $name, $email);
    $stmt->execute();
    $stmt->close();
}

function settings_get_users(mysqli $conn, int $organizationId): array
{
    settings_seed_users($conn, $organizationId);

    $users = [];
    $stmt = $conn->prepare(
        "SELECT * FROM settings_users
         WHERE organization_id = ?
         ORDER BY id ASC"
    );
    if (!$stmt) {
        return $users;
    }

    $stmt->bind_param('i', $organizationId);
    $stmt->execute();
    $result = $stmt->get_result();
    while ($result && ($row = $result->fetch_assoc())) {
        $users[] = $row;
    }
    $stmt->close();

    return $users;
}

function settings_add_user(mysqli $conn, int $organizationId, string $name, string $email, string $roleName): bool
{
    $stmt = $conn->prepare(
        "INSERT INTO settings_users (organization_id, full_name, email, role_name, status, avatar_path)
         VALUES (?, ?, ?, ?, 'ACTIVE', NULL)"
    );
    if (!$stmt) {
        return false;
    }

    $stmt->bind_param('isss', $organizationId, $name, $email, $roleName);
    $ok = $stmt->execute();
    $stmt->close();

    return $ok;
}

function settings_default_pay_schedule(): array
{
    return [
        'pay_frequency' => 'Every month',
        'working_days' => ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'],
        'pay_day_label' => '1st of every month',
        'first_pay_period' => 'May 2025',
        'first_pay_date' => '2025-05-30',
        'working_days_count' => 20,
        'upcoming_payrolls' => [
            ['label' => 'May-2025', 'pay_date' => '2025-05-30'],
            ['label' => 'June-2025', 'pay_date' => '2025-07-01'],
        ],
    ];
}

function settings_get_pay_schedule(mysqli $conn, int $organizationId): array
{
    $defaults = settings_default_pay_schedule();
    $stmt = $conn->prepare("SELECT * FROM pay_schedules WHERE organization_id = ? LIMIT 1");
    if (!$stmt) {
        return $defaults;
    }

    $stmt->bind_param('i', $organizationId);
    $stmt->execute();
    $row = $stmt->get_result()->fetch_assoc();
    $stmt->close();

    if (!$row) {
        $workingDaysJson = json_encode($defaults['working_days'], JSON_UNESCAPED_UNICODE);
        $upcomingJson = json_encode($defaults['upcoming_payrolls'], JSON_UNESCAPED_UNICODE);
        $insert = $conn->prepare(
            "INSERT INTO pay_schedules
             (organization_id, pay_frequency, working_days_json, pay_day_label, first_pay_period, first_pay_date, working_days_count, upcoming_payrolls_json)
             VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
        );
        if ($insert) {
            $insert->bind_param(
                'isssssds',
                $organizationId,
                $defaults['pay_frequency'],
                $workingDaysJson,
                $defaults['pay_day_label'],
                $defaults['first_pay_period'],
                $defaults['first_pay_date'],
                $defaults['working_days_count'],
                $upcomingJson
            );
            $insert->execute();
            $insert->close();
        }

        return $defaults;
    }

    return [
        'pay_frequency' => $row['pay_frequency'] ?: $defaults['pay_frequency'],
        'working_days' => json_decode((string) $row['working_days_json'], true) ?: $defaults['working_days'],
        'pay_day_label' => $row['pay_day_label'] ?: $defaults['pay_day_label'],
        'first_pay_period' => $row['first_pay_period'] ?: $defaults['first_pay_period'],
        'first_pay_date' => $row['first_pay_date'] ?: $defaults['first_pay_date'],
        'working_days_count' => (int) ($row['working_days_count'] ?? $defaults['working_days_count']),
        'upcoming_payrolls' => json_decode((string) $row['upcoming_payrolls_json'], true) ?: $defaults['upcoming_payrolls'],
    ];
}

function settings_default_statutory_components(): array
{
    return [
        'epf' => [
            'enabled' => false,
            'title' => "Employees' Provident Fund",
            'description' => 'Any organisation with 20 or more employees must register for the Employee Provident Fund (EPF) scheme, a retirement benefit plan for all salaried employees.',
            'button_label' => 'Enable EPF',
            'form' => [
                'epf_number' => '',
                'deduction_cycle' => 'Monthly',
                'employee_contribution_rate' => '12% of Actual PF Wage',
                'employer_contribution_rate' => '12% of Actual PF Wage',
                'include_employer_contribution' => true,
                'include_edli' => false,
                'include_admin_charges' => false,
                'override_employee_level' => false,
                'prorate_restricted_pf_wage' => false,
                'consider_salary_components' => true,
            ],
        ],
        'esi' => [
            'enabled' => false,
            'title' => "Employees' State Insurance",
            'description' => 'Configure ESI deduction and employer contribution for eligible employees.',
            'button_label' => 'Enable ESI',
            'form' => [
                'esi_number' => '',
                'deduction_cycle' => 'Monthly',
                'employee_contribution' => '0.75%',
                'employer_contribution' => '3.25%',
                'include_employer_contribution' => false,
            ],
        ],
        'pt' => [
            'enabled' => true,
            'title' => 'Professional Tax',
            'description' => 'This tax is levied on an employee’s income by the State Government. Tax slabs differ in each state.',
            'button_label' => 'Update PT Number',
            'card' => [
                'office' => 'Head Office',
                'pt_number' => '',
                'state' => 'Tamil Nadu',
                'deduction_cycle' => 'Half Yearly',
                'slabs_link' => 'View Tax Slabs',
            ],
        ],
        'lwf' => [
            'enabled' => false,
            'title' => 'Labour Welfare Fund',
            'description' => 'Labour Welfare Fund act ensures social security and improves working conditions for employees.',
            'button_label' => 'Enable',
            'card' => [
                'state' => 'Tamil Nadu',
                'employee_contribution' => '20.00',
                'employer_contribution' => '40.00',
                'deduction_cycle' => 'Yearly',
            ],
        ],
        'bonus' => [
            'enabled' => false,
            'title' => 'Statutory Bonus',
            'description' => 'According to the Payment of Bonus Act, 1965, an eligible employee can receive a statutory bonus of 8.33% (min) to 20% (max) of their salary earned during a financial year.',
            'button_label' => 'Enable Statutory Bonus',
            'form' => [
                'payment_frequency' => 'Monthly',
                'bonus_percentage' => '',
                'state' => 'Tamil Nadu',
            ],
        ],
    ];
}

function settings_get_statutory_component(mysqli $conn, int $organizationId, string $componentCode): array
{
    $defaults = settings_default_statutory_components();
    $base = $defaults[$componentCode] ?? [];
    if (!$base) {
        return [];
    }

    $stmt = $conn->prepare(
        "SELECT * FROM statutory_components
         WHERE organization_id = ? AND component_code = ?
         LIMIT 1"
    );
    if (!$stmt) {
        return $base;
    }

    $stmt->bind_param('is', $organizationId, $componentCode);
    $stmt->execute();
    $row = $stmt->get_result()->fetch_assoc();
    $stmt->close();

    if (!$row) {
        $configJson = json_encode($base, JSON_UNESCAPED_UNICODE);
        $insert = $conn->prepare(
            "INSERT INTO statutory_components (organization_id, component_code, is_enabled, config_json)
             VALUES (?, ?, ?, ?)"
        );
        if ($insert) {
            $enabled = !empty($base['enabled']) ? 1 : 0;
            $insert->bind_param('isis', $organizationId, $componentCode, $enabled, $configJson);
            $insert->execute();
            $insert->close();
        }

        return $base;
    }

    $saved = json_decode((string) $row['config_json'], true);
    if (!is_array($saved)) {
        $saved = [];
    }

    return array_replace_recursive($base, $saved, ['enabled' => (bool) ($row['is_enabled'] ?? 0)]);
}

function settings_save_statutory_component(mysqli $conn, int $organizationId, string $componentCode, array $config): bool
{
    $isEnabled = !empty($config['enabled']) ? 1 : 0;
    $configJson = json_encode($config, JSON_UNESCAPED_UNICODE);
    $stmt = $conn->prepare(
        "INSERT INTO statutory_components (organization_id, component_code, is_enabled, config_json)
         VALUES (?, ?, ?, ?)
         ON DUPLICATE KEY UPDATE
         is_enabled = VALUES(is_enabled),
         config_json = VALUES(config_json)"
    );
    if (!$stmt) {
        return false;
    }

    $stmt->bind_param('isis', $organizationId, $componentCode, $isEnabled, $configJson);
    $ok = $stmt->execute();
    $stmt->close();

    return $ok;
}

function settings_role_permission_sections(): array
{
    return [
        [
            'title' => 'Employee',
            'toggle' => true,
            'columns' => ['Full Access', 'View', 'Create', 'Edit', 'Delete'],
            'rows' => [
                'Basic And Personal Details',
                'Salary Details',
                'Payment Information',
                'Salary Revision',
                'Leave',
                'Attendance',
                'Perquisites',
                'Loan Summary',
                'Reimbursement Summary',
                'Declarations',
            ],
            'notes' => [
                'Provide access to view and download employee Payslips, TDS worksheets and Forms.',
                'Provide access to terminate an employee from the organisation.',
                "Provide access to delete an employee who isn't associated to any Payrun.",
            ],
        ],
        [
            'title' => 'Payroll Run',
            'toggle' => true,
            'columns' => ['Full Access', 'View', 'Create', 'Edit', 'Approve', 'Pay'],
            'rows' => ['Payroll Run'],
        ],
        [
            'title' => 'Loan',
            'toggle' => true,
            'columns' => ['Full Access', 'View', 'Create', 'Edit', 'Approve', 'Record Disbursement'],
            'rows' => ['Loan'],
        ],
        [
            'title' => 'Approvals',
            'toggle' => false,
            'columns' => ['Full Access', 'View', 'Create', 'Edit', 'Delete', 'Approve'],
            'rows' => ['Reimbursements'],
            'notes' => [
                'Provide access to approve Proof Of Investments.',
                'Provide access to approve Salary Revisions.',
                'Provide access to approve Leave Requests.',
                'Provide access to approve Attendance Regularization requests.',
            ],
        ],
        [
            'title' => 'Settings',
            'toggle' => false,
            'checklist' => [
                'Update organization profile',
                'Work Location',
                'Department',
                'Designation',
                'Manage Users',
                'Taxes',
                'Pay Schedule',
                'Salary and Statutory Components',
                'Email Templates',
                'Salary Templates',
                'Reporting Tags',
                'Automation',
                'Custom Fields',
                'Holidays',
                'Leave',
                'Attendance',
                'Provide access to protected data',
                'Integration',
                'Incoming Webhook',
            ],
        ],
        [
            'title' => 'Preferences',
            'toggle' => false,
            'checklist' => [
                'Reimbursement and FBP Settings',
                'IT Declaration Settings',
                'Approval Preferences',
            ],
        ],
        [
            'title' => 'Report',
            'toggle' => false,
            'checklist' => [
                'Payroll Reports',
                'Statutory Reports',
                'Declaration Reports',
                'Deduction Reports',
                'Tax Reports',
                'Loan Reports',
                'Leave Reports',
                'Attendance Reports',
                'Activity Logs',
            ],
        ],
        [
            'title' => 'Documents',
            'toggle' => false,
            'checklist' => [
                'View Documents',
                'Upload Documents',
                'Delete Documents',
                'Manage Folder',
            ],
        ],
    ];
}

function settings_sidebar_link(string $href, string $label, string $activeKey, string $currentKey): string
{
    $class = $activeKey === $currentKey ? 'side-link child active' : 'side-link child';
    return '<a class="' . $class . '" href="' . $href . '">' . htmlspecialchars($label) . '</a>';
}

function settings_render_header(string $orgName, string $closeHref = 'dashboard.php', string $icon = 'fa-file-lines'): void
{
    ?>
    <header class="settings-top">
      <div class="settings-meta">
        <a class="settings-icon" href="<?php echo htmlspecialchars($closeHref); ?>" aria-label="Go back" style="text-decoration:none;color:inherit;">
          <i class="fa-solid <?php echo htmlspecialchars($icon); ?>"></i>
        </a>
        <a class="settings-back" href="settings.php" aria-label="Back">
          <i class="fa-solid fa-angle-left"></i>
        </a>
        <div>
          <div class="settings-title">All Settings</div>
          <div class="settings-sub"><?php echo htmlspecialchars($orgName); ?></div>
        </div>
      </div>
      <div class="settings-search">
        <i class="fa-solid fa-magnifying-glass"></i>
        <input type="text" placeholder="Search settings ( / )" aria-label="Search settings">
      </div>
      <a class="settings-close" href="<?php echo htmlspecialchars($closeHref); ?>">Close Settings <span class="close-x">x</span></a>
    </header>
    <?php
}

function settings_render_sidebar(string $currentKey): void
{
    ?>
    <aside class="profile-sidebar">
      <div class="side-section-title">Organisation Settings</div>
      <div class="side-group expanded">
        <button class="side-group-toggle" type="button">
          <span class="caret down"><i class="fa-solid fa-chevron-down"></i></span>
          Organisation
        </button>
        <?php echo settings_sidebar_link('orgprofile.php', 'Profile', 'profile', $currentKey); ?>
        <?php echo settings_sidebar_link('branding.php', 'Branding', 'branding', $currentKey); ?>
        <?php echo settings_sidebar_link('locations.php', 'Work Locations', 'locations', $currentKey); ?>
        <?php echo settings_sidebar_link('departments.php', 'Departments', 'departments', $currentKey); ?>
        <?php echo settings_sidebar_link('designations.php', 'Designations', 'designations', $currentKey); ?>
        <?php echo settings_sidebar_link('subscriptions.php', 'Subscriptions', 'subscriptions', $currentKey); ?>
      </div>
      <div class="side-group expanded">
        <button class="side-group-toggle" type="button">
          <span class="caret down"><i class="fa-solid fa-chevron-down"></i></span>
          Users and Roles
        </button>
        <?php echo settings_sidebar_link('users.php', 'Users', 'users', $currentKey); ?>
        <?php echo settings_sidebar_link('roles.php', 'Roles', 'roles', $currentKey); ?>
      </div>
      <div class="side-group expanded">
        <button class="side-group-toggle" type="button">
          <span class="caret down"><i class="fa-solid fa-chevron-down"></i></span>
          Taxes
        </button>
        <?php echo settings_sidebar_link('tax_details.php', 'Tax Details', 'taxes', $currentKey); ?>
      </div>
      <div class="side-group expanded">
        <button class="side-group-toggle" type="button">
          <span class="caret down"><i class="fa-solid fa-chevron-down"></i></span>
          Setup &amp; Configurations
        </button>
        <?php echo settings_sidebar_link('pay_schedule.php', 'Pay Schedule', 'pay_schedule', $currentKey); ?>
        <?php echo settings_sidebar_link('statutory_components.php', 'Statutory Components', 'statutory_components', $currentKey); ?>
        <?php echo settings_sidebar_link('salary_components.php', 'Salary Components', 'salary_components', $currentKey); ?>
        <?php echo settings_sidebar_link('employee_portal.php', 'Employee Portal', 'employee_portal', $currentKey); ?>
        <?php echo settings_sidebar_link('claims_declarations.php', 'Claims and Declarations', 'claims_declarations', $currentKey); ?>
      </div>
      <div class="side-group expanded">
        <button class="side-group-toggle" type="button">
          <span class="caret down"><i class="fa-solid fa-chevron-down"></i></span>
          Customisations
        </button>
        <?php echo settings_sidebar_link('email_templates.php', 'Email Templates', 'email_templates', $currentKey); ?>
        <?php echo settings_sidebar_link('sender_email_preferences.php', 'Sender Email Preferences', 'sender_email_preferences', $currentKey); ?>
        <?php echo settings_sidebar_link('salary_templates.php', 'Salary Templates', 'salary_templates', $currentKey); ?>
        <?php echo settings_sidebar_link('salary_revision_template.php', 'PDF Templates', 'pdf_templates', $currentKey); ?>
        <?php echo settings_sidebar_link('reporting_tags.php', 'Reporting Tags', 'reporting_tags', $currentKey); ?>
      </div>
      <div class="side-group expanded">
        <button class="side-group-toggle" type="button">
          <span class="caret down"><i class="fa-solid fa-chevron-down"></i></span>
          Automations
        </button>
        <?php echo settings_sidebar_link('workflow_rules.php', 'Workflow Rules', 'workflow_rules', $currentKey); ?>
        <?php echo settings_sidebar_link('actions.php', 'Actions', 'actions', $currentKey); ?>
        <?php echo settings_sidebar_link('schedules.php', 'Schedules', 'schedules', $currentKey); ?>
        <?php echo settings_sidebar_link('workflow_logs.php', 'Workflow Logs', 'workflow_logs', $currentKey); ?>
      </div>

      <div class="side-section-title">Module Settings</div>
      <div class="side-group expanded">
        <button class="side-group-toggle" type="button">
          <span class="caret down"><i class="fa-solid fa-chevron-down"></i></span>
          General
        </button>
        <?php echo settings_sidebar_link('employees_contractors.php', 'Employees & Contractors', 'employees_contractors', $currentKey); ?>
        <?php echo settings_sidebar_link('payrun_settings.php', 'Pay Runs', 'payrun_settings', $currentKey); ?>
      </div>
      <div class="side-group expanded">
        <button class="side-group-toggle" type="button">
          <span class="caret down"><i class="fa-solid fa-chevron-down"></i></span>
          Payments
        </button>
        <?php echo settings_sidebar_link('direct_deposits.php', 'Direct Deposits', 'direct_deposits', $currentKey); ?>
      </div>

      <div class="side-section-title">Extensions &amp; Developer Data</div>
      <div class="side-group expanded">
        <button class="side-group-toggle" type="button">
          <span class="caret down"><i class="fa-solid fa-chevron-down"></i></span>
          Integrations
        </button>
        <?php echo settings_sidebar_link('zoho_apps.php', 'Zoho Apps', 'zoho_apps', $currentKey); ?>
      </div>
      <div class="side-group expanded">
        <button class="side-group-toggle" type="button">
          <span class="caret down"><i class="fa-solid fa-chevron-down"></i></span>
          Developer Data
        </button>
        <?php echo settings_sidebar_link('connections.php', 'Connections', 'connections', $currentKey); ?>
        <?php echo settings_sidebar_link('incoming_webhooks.php', 'Incoming Webhooks', 'incoming_webhooks', $currentKey); ?>
        <?php echo settings_sidebar_link('data_backup.php', 'Data Backup', 'data_backup', $currentKey); ?>
      </div>
    </aside>
    <?php
}

function settings_flash_message(): array
{
    $flash = $_SESSION['settings_flash'] ?? ['type' => '', 'message' => ''];
    unset($_SESSION['settings_flash']);
    return $flash;
}

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