<?php
if ( ! defined( 'ABSPATH' ) ) exit;

/**
 * ACC_Payroll_Controller
 * REST API — Payroll (حقوق و دستمزد)
 *
 * Flow:
 *   Employee setup → Component definitions → Assign components to employees
 *   → Create period → Generate slips (calculate) → Finalize (SALARY_RECOGNITION event)
 *   → Pay (SALARY_PAYMENT event)
 */
class ACC_Payroll_Controller {

    private static function p(): string { return 'acc_jwt_check_permission'; }

    public static function register_routes(): void {
        $ns = 'acc/v1';
        $p  = self::p();

        // ── Employees ───────────────────────────────────────────
        register_rest_route( $ns, '/payroll/employees', [
            [ 'methods' => 'GET',  'callback' => [ self::class, 'get_employees' ],   'permission_callback' => $p ],
            [ 'methods' => 'POST', 'callback' => [ self::class, 'create_employee' ], 'permission_callback' => $p ],
        ] );
        register_rest_route( $ns, '/payroll/employees/(?P<id>\d+)', [
            [ 'methods' => 'PUT',    'callback' => [ self::class, 'update_employee' ], 'permission_callback' => $p ],
            [ 'methods' => 'DELETE', 'callback' => [ self::class, 'delete_employee' ], 'permission_callback' => $p ],
        ] );
        register_rest_route( $ns, '/payroll/employees/(?P<id>\d+)/components', [
            [ 'methods' => 'GET',  'callback' => [ self::class, 'get_emp_components' ],  'permission_callback' => $p ],
            [ 'methods' => 'POST', 'callback' => [ self::class, 'save_emp_components' ], 'permission_callback' => $p ],
        ] );

        // ── Salary Components ────────────────────────────────────
        register_rest_route( $ns, '/payroll/components', [
            [ 'methods' => 'GET',  'callback' => [ self::class, 'get_components' ],   'permission_callback' => $p ],
            [ 'methods' => 'POST', 'callback' => [ self::class, 'create_component' ], 'permission_callback' => $p ],
        ] );
        register_rest_route( $ns, '/payroll/components/(?P<id>\d+)', [
            [ 'methods' => 'PUT',    'callback' => [ self::class, 'update_component' ], 'permission_callback' => $p ],
            [ 'methods' => 'DELETE', 'callback' => [ self::class, 'delete_component' ], 'permission_callback' => $p ],
        ] );

        // ── Payroll Periods ─────────────────────────────────────
        register_rest_route( $ns, '/payroll/periods', [
            [ 'methods' => 'GET',  'callback' => [ self::class, 'get_periods' ],   'permission_callback' => $p ],
            [ 'methods' => 'POST', 'callback' => [ self::class, 'create_period' ], 'permission_callback' => $p ],
        ] );
        register_rest_route( $ns, '/payroll/periods/(?P<id>\d+)', [
            [ 'methods' => 'GET', 'callback' => [ self::class, 'get_period' ], 'permission_callback' => $p ],
        ] );
        register_rest_route( $ns, '/payroll/periods/(?P<id>\d+)/slips', [
            [ 'methods' => 'GET', 'callback' => [ self::class, 'get_period_slips' ], 'permission_callback' => $p ],
        ] );
        register_rest_route( $ns, '/payroll/periods/(?P<id>\d+)/generate', [
            [ 'methods' => 'POST', 'callback' => [ self::class, 'generate_slips' ], 'permission_callback' => $p ],
        ] );
        register_rest_route( $ns, '/payroll/periods/(?P<id>\d+)/finalize', [
            [ 'methods' => 'POST', 'callback' => [ self::class, 'finalize_period' ], 'permission_callback' => $p ],
        ] );
        register_rest_route( $ns, '/payroll/periods/(?P<id>\d+)/pay', [
            [ 'methods' => 'POST', 'callback' => [ self::class, 'pay_period' ], 'permission_callback' => $p ],
        ] );

        // ── Individual Slip ─────────────────────────────────────
        register_rest_route( $ns, '/payroll/slips/(?P<id>\d+)', [
            [ 'methods' => 'GET', 'callback' => [ self::class, 'get_slip_detail' ], 'permission_callback' => $p ],
        ] );

        // ── Install ─────────────────────────────────────────────
        register_rest_route( $ns, '/payroll/install', [
            [ 'methods' => 'POST', 'callback' => [ self::class, 'run_install' ], 'permission_callback' => $p ],
        ] );
    }

    /* ==========================================================
       EMPLOYEES
    ========================================================== */

    public static function get_employees( WP_REST_Request $req ): array {
        global $wpdb;
        $search = sanitize_text_field( $req->get_param( 'search' ) ?? '' );
        $conds  = [ 'is_active = 1' ];
        if ( $search ) {
            $conds[] = $wpdb->prepare(
                '(full_name LIKE %s OR emp_code LIKE %s OR national_id LIKE %s)',
                "%{$search}%", "%{$search}%", "%{$search}%"
            );
        }
        $where = 'WHERE ' . implode( ' AND ', $conds );
        $rows  = $wpdb->get_results(
            "SELECT * FROM {$wpdb->prefix}ai_payroll_employees {$where} ORDER BY full_name ASC",
            ARRAY_A
        );
        return [ 'success' => true, 'data' => $rows ?: [] ];
    }

    public static function create_employee( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;
        $hire = sanitize_text_field( $req->get_param( 'hire_date' ) ?? '' );
        $wpdb->insert( $wpdb->prefix . 'ai_payroll_employees', [
            'emp_code'    => sanitize_text_field( $req->get_param( 'emp_code' )    ?? '' ),
            'full_name'   => sanitize_text_field( $req->get_param( 'full_name' ) ),
            'national_id' => sanitize_text_field( $req->get_param( 'national_id' ) ?? '' ),
            'position'    => sanitize_text_field( $req->get_param( 'position' )    ?? '' ),
            'department'  => sanitize_text_field( $req->get_param( 'department' )  ?? '' ),
            'hire_date'   => $hire ?: null,
            'bank_iban'   => sanitize_text_field( $req->get_param( 'bank_iban' )   ?? '' ),
            'is_active'   => 1,
        ] );
        return new WP_REST_Response( [ 'success' => true, 'id' => $wpdb->insert_id ], 201 );
    }

    public static function update_employee( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;
        $id   = (int) $req->get_param( 'id' );
        $data = [];
        foreach ( [ 'emp_code', 'full_name', 'national_id', 'position', 'department', 'bank_iban' ] as $f ) {
            if ( $req->has_param( $f ) ) $data[ $f ] = sanitize_text_field( $req->get_param( $f ) );
        }
        if ( $req->has_param( 'hire_date' ) ) {
            $data['hire_date'] = sanitize_text_field( $req->get_param( 'hire_date' ) ) ?: null;
        }
        if ( $req->has_param( 'is_active' ) ) $data['is_active'] = (int) $req->get_param( 'is_active' );
        if ( ! empty( $data ) ) $wpdb->update( $wpdb->prefix . 'ai_payroll_employees', $data, [ 'id' => $id ] );
        return new WP_REST_Response( [ 'success' => true ], 200 );
    }

    public static function delete_employee( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;
        $wpdb->update(
            $wpdb->prefix . 'ai_payroll_employees',
            [ 'is_active' => 0 ],
            [ 'id' => (int) $req->get_param( 'id' ) ]
        );
        return new WP_REST_Response( [ 'success' => true ], 200 );
    }

    public static function get_emp_components( WP_REST_Request $req ): array {
        global $wpdb;
        $emp_id = (int) $req->get_param( 'id' );
        $rows   = $wpdb->get_results( $wpdb->prepare(
            "SELECT ec.*, c.name AS comp_name, c.type AS comp_type,
                    c.calc_type, c.sort_order, c.is_insurance_base, c.is_taxable
             FROM {$wpdb->prefix}ai_payroll_emp_components ec
             JOIN {$wpdb->prefix}ai_payroll_components c ON c.id = ec.component_id
             WHERE ec.employee_id = %d AND c.is_active = 1
             ORDER BY c.sort_order ASC",
            $emp_id
        ), ARRAY_A );
        return [ 'success' => true, 'data' => $rows ?: [] ];
    }

    /**
     * Replace all component assignments for an employee.
     * Expects JSON body: { "components": [{"component_id": N, "amount": 0}, ...] }
     */
    public static function save_emp_components( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;
        $emp_id     = (int) $req->get_param( 'id' );
        $components = $req->get_param( 'components' );

        if ( ! is_array( $components ) ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'پارامتر components باید آرایه باشد' ], 400 );
        }

        // Verify employee exists
        $exists = $wpdb->get_var( $wpdb->prepare(
            "SELECT id FROM {$wpdb->prefix}ai_payroll_employees WHERE id=%d AND is_active=1",
            $emp_id
        ) );
        if ( ! $exists ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'پرسنل یافت نشد' ], 404 );
        }

        // Delete all existing assignments
        $wpdb->delete( $wpdb->prefix . 'ai_payroll_emp_components', [ 'employee_id' => $emp_id ] );

        // Re-insert
        foreach ( $components as $item ) {
            $comp_id = (int) ( $item['component_id'] ?? 0 );
            $amount  = (float) ( $item['amount'] ?? 0 );
            if ( ! $comp_id || $amount < 0 ) continue;
            $wpdb->insert( $wpdb->prefix . 'ai_payroll_emp_components', [
                'employee_id'  => $emp_id,
                'component_id' => $comp_id,
                'amount'       => $amount,
            ] );
        }

        return new WP_REST_Response( [ 'success' => true ], 200 );
    }

    /* ==========================================================
       SALARY COMPONENTS
    ========================================================== */

    public static function get_components( WP_REST_Request $req ): array {
        global $wpdb;
        $rows = $wpdb->get_results(
            "SELECT * FROM {$wpdb->prefix}ai_payroll_components WHERE is_active=1 ORDER BY sort_order ASC, id ASC",
            ARRAY_A
        );
        return [ 'success' => true, 'data' => $rows ?: [] ];
    }

    public static function create_component( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;
        $wpdb->insert( $wpdb->prefix . 'ai_payroll_components', [
            'code'              => sanitize_text_field( $req->get_param( 'code' )          ?? '' ),
            'name'              => sanitize_text_field( $req->get_param( 'name' ) ),
            'type'              => in_array( $req->get_param( 'type' ), [ 'earning', 'deduction' ] ) ? $req->get_param( 'type' ) : 'earning',
            'calc_type'         => in_array( $req->get_param( 'calc_type' ), [ 'fixed', 'percent_of_base' ] ) ? $req->get_param( 'calc_type' ) : 'fixed',
            'default_value'     => (float) ( $req->get_param( 'default_value' )     ?? 0 ),
            'is_taxable'        => (int) ( $req->get_param( 'is_taxable' )           ?? 0 ),
            'is_insurance_base' => (int) ( $req->get_param( 'is_insurance_base' )    ?? 0 ),
            'sort_order'        => (int) ( $req->get_param( 'sort_order' )           ?? 0 ),
            'is_active'         => 1,
        ] );
        return new WP_REST_Response( [ 'success' => true, 'id' => $wpdb->insert_id ], 201 );
    }

    public static function update_component( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;
        $id   = (int) $req->get_param( 'id' );
        $data = [];
        foreach ( [ 'code', 'name' ] as $f ) {
            if ( $req->has_param( $f ) ) $data[ $f ] = sanitize_text_field( $req->get_param( $f ) );
        }
        if ( $req->has_param( 'type' ) )              $data['type']              = in_array( $req->get_param( 'type' ), [ 'earning', 'deduction' ] ) ? $req->get_param( 'type' ) : 'earning';
        if ( $req->has_param( 'calc_type' ) )         $data['calc_type']         = in_array( $req->get_param( 'calc_type' ), [ 'fixed', 'percent_of_base' ] ) ? $req->get_param( 'calc_type' ) : 'fixed';
        if ( $req->has_param( 'default_value' ) )     $data['default_value']     = (float) $req->get_param( 'default_value' );
        if ( $req->has_param( 'is_taxable' ) )        $data['is_taxable']        = (int) $req->get_param( 'is_taxable' );
        if ( $req->has_param( 'is_insurance_base' ) ) $data['is_insurance_base'] = (int) $req->get_param( 'is_insurance_base' );
        if ( $req->has_param( 'sort_order' ) )        $data['sort_order']        = (int) $req->get_param( 'sort_order' );
        if ( $req->has_param( 'is_active' ) )         $data['is_active']         = (int) $req->get_param( 'is_active' );
        if ( ! empty( $data ) ) $wpdb->update( $wpdb->prefix . 'ai_payroll_components', $data, [ 'id' => $id ] );
        return new WP_REST_Response( [ 'success' => true ], 200 );
    }

    public static function delete_component( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;
        $wpdb->update( $wpdb->prefix . 'ai_payroll_components', [ 'is_active' => 0 ], [ 'id' => (int) $req->get_param( 'id' ) ] );
        return new WP_REST_Response( [ 'success' => true ], 200 );
    }

    /* ==========================================================
       PAYROLL PERIODS
    ========================================================== */

    public static function get_periods( WP_REST_Request $req ): array {
        global $wpdb;
        $project_id = $req->get_param('project_id') ? (int)$req->get_param('project_id') : null;
        $where      = $project_id ? $wpdb->prepare('WHERE pr.project_id=%d', $project_id) : '';
        $rows       = $wpdb->get_results(
            "SELECT pr.*, p.name AS project_name
             FROM {$wpdb->prefix}ai_payroll_periods pr
             LEFT JOIN {$wpdb->prefix}pc_projects p ON p.id = pr.project_id
             {$where}
             ORDER BY pr.year DESC, pr.month DESC",
            ARRAY_A
        );
        return [ 'success' => true, 'data' => $rows ?: [] ];
    }

    public static function create_period( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;
        $year  = (int) $req->get_param( 'year' );
        $month = (int) $req->get_param( 'month' );

        if ( $year < 1300 || $year > 1500 || $month < 1 || $month > 12 ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'سال یا ماه نامعتبر است' ], 400 );
        }

        $period_code = $year . '-' . str_pad( $month, 2, '0', STR_PAD_LEFT );
        $exists      = $wpdb->get_var( $wpdb->prepare(
            "SELECT id FROM {$wpdb->prefix}ai_payroll_periods WHERE period_code=%s", $period_code
        ) );
        if ( $exists ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'این دوره قبلاً ثبت شده است' ], 409 );
        }

        $persian_months = [ '', 'فروردین', 'اردیبهشت', 'خرداد', 'تیر', 'مرداد', 'شهریور', 'مهر', 'آبان', 'آذر', 'دی', 'بهمن', 'اسفند' ];
        $period_name    = ( $persian_months[ $month ] ?? $month ) . ' ' . $year;

        $project_id = $req->get_param('project_id') ? (int)$req->get_param('project_id') : null;

        $wpdb->insert( $wpdb->prefix . 'ai_payroll_periods', [
            'period_code' => $period_code,
            'period_name' => $period_name,
            'year'        => $year,
            'month'       => $month,
            'status'      => 'draft',
            'project_id'  => $project_id,
        ] );
        return new WP_REST_Response( [ 'success' => true, 'id' => $wpdb->insert_id, 'period_name' => $period_name ], 201 );
    }

    public static function get_period( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;
        $row = $wpdb->get_row( $wpdb->prepare(
            "SELECT * FROM {$wpdb->prefix}ai_payroll_periods WHERE id=%d",
            (int) $req->get_param( 'id' )
        ), ARRAY_A );
        if ( ! $row ) return new WP_REST_Response( [ 'success' => false, 'message' => 'یافت نشد' ], 404 );
        return new WP_REST_Response( [ 'success' => true, 'data' => $row ], 200 );
    }

    public static function get_period_slips( WP_REST_Request $req ): array {
        global $wpdb;
        $period_id = (int) $req->get_param( 'id' );
        $rows      = $wpdb->get_results( $wpdb->prepare(
            "SELECT s.*, e.position, e.department, e.emp_code
             FROM {$wpdb->prefix}ai_payroll_slips s
             LEFT JOIN {$wpdb->prefix}ai_payroll_employees e ON e.id = s.employee_id
             WHERE s.period_id = %d
             ORDER BY s.emp_name ASC",
            $period_id
        ), ARRAY_A );
        return [ 'success' => true, 'data' => $rows ?: [] ];
    }

    public static function get_slip_detail( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;
        $id   = (int) $req->get_param( 'id' );
        $slip = $wpdb->get_row( $wpdb->prepare(
            "SELECT s.*, p.period_name, p.period_code
             FROM {$wpdb->prefix}ai_payroll_slips s
             JOIN {$wpdb->prefix}ai_payroll_periods p ON p.id = s.period_id
             WHERE s.id = %d",
            $id
        ), ARRAY_A );
        if ( ! $slip ) return new WP_REST_Response( [ 'success' => false, 'message' => 'فیش یافت نشد' ], 404 );

        $items        = $wpdb->get_results( $wpdb->prepare(
            "SELECT * FROM {$wpdb->prefix}ai_payroll_slip_items WHERE slip_id=%d ORDER BY sort_order ASC, type DESC",
            $id
        ), ARRAY_A );
        $slip['items'] = $items ?: [];
        return new WP_REST_Response( [ 'success' => true, 'data' => $slip ], 200 );
    }

    /* ==========================================================
       GENERATE SLIPS
    ========================================================== */

    public static function generate_slips( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;
        $period_id = (int) $req->get_param( 'id' );

        $period = $wpdb->get_row( $wpdb->prepare(
            "SELECT * FROM {$wpdb->prefix}ai_payroll_periods WHERE id=%d", $period_id
        ), ARRAY_A );

        if ( ! $period ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'دوره یافت نشد' ], 404 );
        }
        if ( ! in_array( $period['status'], [ 'draft', 'generated' ], true ) ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'وضعیت دوره اجازه تولید مجدد فیش را نمی‌دهد' ], 400 );
        }

        // Delete existing slips for this period (safe cascade via JOIN DELETE)
        $wpdb->query( $wpdb->prepare(
            "DELETE si FROM {$wpdb->prefix}ai_payroll_slip_items si
             INNER JOIN {$wpdb->prefix}ai_payroll_slips s ON s.id = si.slip_id
             WHERE s.period_id = %d",
            $period_id
        ) );
        $wpdb->delete( $wpdb->prefix . 'ai_payroll_slips', [ 'period_id' => $period_id ] );

        // Fetch active employees
        $employees = $wpdb->get_results(
            "SELECT * FROM {$wpdb->prefix}ai_payroll_employees WHERE is_active=1 ORDER BY full_name ASC",
            ARRAY_A
        );

        if ( empty( $employees ) ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'هیچ پرسنل فعالی یافت نشد' ], 400 );
        }

        $total_gross      = 0.0;
        $total_deductions = 0.0;
        $total_net        = 0.0;
        $slip_count       = 0;
        $skipped          = 0;

        foreach ( $employees as $emp ) {
            $emp_comps = $wpdb->get_results( $wpdb->prepare(
                "SELECT ec.amount, ec.component_id,
                        c.name AS comp_name, c.type AS comp_type,
                        c.calc_type, c.sort_order
                 FROM {$wpdb->prefix}ai_payroll_emp_components ec
                 JOIN {$wpdb->prefix}ai_payroll_components c ON c.id = ec.component_id
                 WHERE ec.employee_id = %d AND c.is_active = 1
                 ORDER BY c.sort_order ASC",
                $emp['id']
            ), ARRAY_A );

            if ( empty( $emp_comps ) ) {
                $skipped++;
                continue; // skip employees without salary components
            }

            // Determine the base salary (first fixed earning — used for percent_of_base)
            $base = 0.0;
            foreach ( $emp_comps as $c ) {
                if ( $c['comp_type'] === 'earning' && $c['calc_type'] === 'fixed' ) {
                    $base = (float) $c['amount'];
                    break;
                }
            }

            $gross      = 0.0;
            $deductions = 0.0;
            $items      = [];

            foreach ( $emp_comps as $c ) {
                $amt = (float) $c['amount'];
                if ( $c['calc_type'] === 'percent_of_base' ) {
                    $amt = round( $base * ( $amt / 100.0 ), 2 );
                }

                if ( $c['comp_type'] === 'earning' ) {
                    $gross += $amt;
                } else {
                    $deductions += $amt;
                }

                $items[] = [
                    'component_id'   => (int) $c['component_id'],
                    'component_name' => $c['comp_name'],
                    'type'           => $c['comp_type'],
                    'amount'         => $amt,
                    'sort_order'     => (int) $c['sort_order'],
                ];
            }

            $net = $gross - $deductions;

            $wpdb->insert( $wpdb->prefix . 'ai_payroll_slips', [
                'period_id'        => $period_id,
                'employee_id'      => (int) $emp['id'],
                'emp_name'         => $emp['full_name'],
                'gross_salary'     => $gross,
                'total_deductions' => $deductions,
                'net_salary'       => $net,
                'status'           => 'draft',
            ] );
            $slip_id = $wpdb->insert_id;

            foreach ( $items as $item ) {
                $wpdb->insert( $wpdb->prefix . 'ai_payroll_slip_items',
                    array_merge( $item, [ 'slip_id' => $slip_id ] )
                );
            }

            $total_gross      += $gross;
            $total_deductions += $deductions;
            $total_net        += $net;
            $slip_count++;
        }

        $wpdb->update( $wpdb->prefix . 'ai_payroll_periods', [
            'status'           => 'generated',
            'total_gross'      => round( $total_gross, 4 ),
            'total_deductions' => round( $total_deductions, 4 ),
            'total_net'        => round( $total_net, 4 ),
            'slip_count'       => $slip_count,
        ], [ 'id' => $period_id ] );

        return new WP_REST_Response( [
            'success'    => true,
            'slip_count' => $slip_count,
            'skipped'    => $skipped,
            'total_net'  => $total_net,
            'total_gross'=> $total_gross,
        ], 200 );
    }

    /* ==========================================================
       FINALIZE PERIOD  →  SALARY_RECOGNITION event
    ========================================================== */

    public static function finalize_period( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;
        $period_id = (int) $req->get_param( 'id' );

        $period = $wpdb->get_row( $wpdb->prepare(
            "SELECT * FROM {$wpdb->prefix}ai_payroll_periods WHERE id=%d", $period_id
        ), ARRAY_A );

        if ( ! $period ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'دوره یافت نشد' ], 404 );
        }
        if ( $period['status'] !== 'generated' ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'ابتدا فیش‌ها را تولید کنید' ], 400 );
        }

        require_once ACC_PATH . 'includes/helpers/acc-event-engine.php';
        $payload = [
            'net_amount'       => (float) $period['total_net'],
            'gross_amount'     => (float) $period['total_gross'],
            'deduction_amount' => (float) $period['total_deductions'],
            'period_code'      => $period['period_code'],
            'period_name'      => $period['period_name'],
            'slip_count'       => (int) $period['slip_count'],
        ];

        $result = Acc_Event_Engine::emit_and_post([
            'event_type'      => 'SALARY_RECOGNITION',
            'amount'          => (float) $period['total_net'],
            'payload'         => $payload,
            'reference_type'  => 'payroll_period',
            'reference_id'    => $period_id,
            'idempotency_key' => 'PAY_REC_' . $period_id,
            'source_module'   => 'payroll',
        ]);

        if ( ! $result['success'] ) {
            return new WP_REST_Response( [
                'success' => false,
                'message' => $result['message'] ?? 'خطا در ثبت رویداد حسابداری',
            ], 400 );
        }

        $event_id = (int) $result['event_id'];

        // Finalize period and all its slips
        $wpdb->update( $wpdb->prefix . 'ai_payroll_periods', [
            'status'               => 'finalized',
            'recognition_event_id' => $event_id,
        ], [ 'id' => $period_id ] );

        $wpdb->update( $wpdb->prefix . 'ai_payroll_slips',
            [ 'status' => 'final' ],
            [ 'period_id' => $period_id ]
        );

        return new WP_REST_Response( [
            'success'  => true,
            'event_id' => $event_id,
            'message'  => 'دوره نهایی شد و رویداد حسابداری شناخت حقوق ثبت گردید',
        ], 200 );
    }

    /* ==========================================================
       PAY PERIOD  →  SALARY_PAYMENT event
    ========================================================== */

    public static function pay_period( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;
        $period_id = (int) $req->get_param( 'id' );

        $period = $wpdb->get_row( $wpdb->prepare(
            "SELECT * FROM {$wpdb->prefix}ai_payroll_periods WHERE id=%d", $period_id
        ), ARRAY_A );

        if ( ! $period ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'دوره یافت نشد' ], 404 );
        }
        if ( $period['status'] !== 'finalized' ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'دوره باید ابتدا نهایی شده باشد' ], 400 );
        }

        require_once ACC_PATH . 'includes/helpers/acc-event-engine.php';
        $payload = [
            'net_amount'  => (float) $period['total_net'],
            'period_code' => $period['period_code'],
            'period_name' => $period['period_name'],
        ];

        $result = Acc_Event_Engine::emit_and_post([
            'event_type'      => 'SALARY_PAYMENT',
            'amount'          => (float) $period['total_net'],
            'payload'         => $payload,
            'reference_type'  => 'payroll_payment',
            'reference_id'    => $period_id,
            'idempotency_key' => 'PAY_PAY_' . $period_id,
            'source_module'   => 'payroll',
        ]);

        if ( ! $result['success'] ) {
            return new WP_REST_Response( [
                'success' => false,
                'message' => $result['message'] ?? 'خطا در ثبت رویداد پرداخت',
            ], 400 );
        }

        $event_id = (int) $result['event_id'];

        $wpdb->update( $wpdb->prefix . 'ai_payroll_periods', [
            'status'           => 'paid',
            'payment_event_id' => $event_id,
        ], [ 'id' => $period_id ] );

        return new WP_REST_Response( [
            'success'  => true,
            'event_id' => $event_id,
            'message'  => 'پرداخت حقوق ثبت و رویداد حسابداری صادر گردید',
        ], 200 );
    }

    /* ==========================================================
       INSTALL
    ========================================================== */

    public static function run_install( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;
        $sql_file = ACC_PATH . 'includes/sql/payroll_schema.sql';
        if ( ! file_exists( $sql_file ) ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'فایل SQL یافت نشد' ], 404 );
        }
        $sql        = file_get_contents( $sql_file );
        $statements = array_filter( array_map( 'trim', explode( ';', $sql ) ) );
        $errors     = [];
        foreach ( $statements as $stmt ) {
            if ( empty( $stmt ) || str_starts_with( $stmt, '--' ) ) continue;
            $result = $wpdb->query( $stmt );
            if ( $result === false && ! empty( $wpdb->last_error ) ) {
                $errors[] = $wpdb->last_error;
            }
        }
        if ( ! empty( $errors ) ) {
            return new WP_REST_Response( [ 'success' => false, 'errors' => $errors ], 500 );
        }
        return new WP_REST_Response( [ 'success' => true, 'message' => 'جداول حقوق و دستمزد با موفقیت نصب شد' ], 200 );
    }
}
