<?php
/**
 * SR_Controller — خدمات میدانی (سردخانه / چیلر)
 * درخواست خدمت + دستگاه + گزارش فنی (SRD)
 */

if ( ! defined( 'ABSPATH' ) ) exit;

class SR_Controller {

    /** @var array<string,string> */
    public static $SERVICE_TYPES = [
        'phone_consult'      => 'مشاوره تلفنی',
        'online_consult'     => 'مشاوره آنلاین',
        'site_visit'         => 'بازدید کارشناسی',
        'troubleshooting'    => 'عیب‌یابی',
        'repair'             => 'تعمیر',
        'pm'                 => 'سرویس دوره‌ای (PM)',
        'overhaul'           => 'اورهال',
        'install'            => 'نصب و راه‌اندازی',
        'parts_supply'       => 'تأمین قطعات',
        'equipment_purchase' => 'خرید تجهیزات',
        'contract_support'   => 'پشتیبانی قراردادی',
    ];

    /** @var array<string,string> */
    public static $REQUEST_STATUS = [
        'submitted'   => 'ثبت شده',
        'assigned'    => 'تخصیص یافته',
        'in_progress' => 'در حال انجام',
        'completed'   => 'تکمیل',
        'cancelled'   => 'لغو',
    ];

    public static function register_routes(): void {
        $ns  = 'acc/v1';
        $jwt = 'acc_jwt_check_permission';
        $pub = '__return_true';

        /* ── Visitor (ویجت مشتری) ── */
        register_rest_route( $ns, '/service/visitor/request', [
            'methods'             => 'POST',
            'callback'            => [ __CLASS__, 'visitor_create_request' ],
            'permission_callback' => $pub,
        ] );
        register_rest_route( $ns, '/service/visitor/cases', [
            'methods'             => 'GET',
            'callback'            => [ __CLASS__, 'visitor_list_cases' ],
            'permission_callback' => $pub,
        ] );
        register_rest_route( $ns, '/service/visitor/track/(?P<code>[A-Z0-9]+)', [
            'methods'             => 'GET',
            'callback'            => [ __CLASS__, 'visitor_track' ],
            'permission_callback' => $pub,
        ] );
        register_rest_route( $ns, '/service/public/report/(?P<token>[a-zA-Z0-9]+)', [
            'methods'             => 'GET',
            'callback'            => [ __CLASS__, 'public_report' ],
            'permission_callback' => $pub,
        ] );

        /* ── ERP — درخواست‌ها ── */
        register_rest_route( $ns, '/service/requests', [
            [ 'methods' => 'GET',  'callback' => [ __CLASS__, 'list_requests' ], 'permission_callback' => $jwt ],
            [ 'methods' => 'POST', 'callback' => [ __CLASS__, 'create_request' ], 'permission_callback' => $jwt ],
        ] );
        register_rest_route( $ns, '/service/requests/(?P<id>\d+)', [
            [ 'methods' => 'GET', 'callback' => [ __CLASS__, 'get_request' ], 'permission_callback' => $jwt ],
            [ 'methods' => 'PUT', 'callback' => [ __CLASS__, 'update_request' ], 'permission_callback' => $jwt ],
        ] );
        register_rest_route( $ns, '/service/requests/(?P<id>\d+)/assign', [
            'methods'             => 'POST',
            'callback'            => [ __CLASS__, 'assign_request' ],
            'permission_callback' => $jwt,
        ] );

        /* ── ERP — دستگاه‌ها ── */
        register_rest_route( $ns, '/service/devices', [
            [ 'methods' => 'GET',  'callback' => [ __CLASS__, 'list_devices' ], 'permission_callback' => $jwt ],
            [ 'methods' => 'POST', 'callback' => [ __CLASS__, 'create_device' ], 'permission_callback' => $jwt ],
        ] );
        register_rest_route( $ns, '/service/devices/(?P<id>\d+)', [
            [ 'methods' => 'GET', 'callback' => [ __CLASS__, 'get_device' ], 'permission_callback' => $jwt ],
            [ 'methods' => 'PUT', 'callback' => [ __CLASS__, 'update_device' ], 'permission_callback' => $jwt ],
        ] );
        register_rest_route( $ns, '/service/devices/(?P<id>\d+)/timeline', [
            'methods'             => 'GET',
            'callback'            => [ __CLASS__, 'device_timeline' ],
            'permission_callback' => $jwt,
        ] );

        /* ── ERP — گزارش فنی (SRD) ── */
        register_rest_route( $ns, '/service/reports', [
            [ 'methods' => 'GET',  'callback' => [ __CLASS__, 'list_reports' ], 'permission_callback' => $jwt ],
            [ 'methods' => 'POST', 'callback' => [ __CLASS__, 'create_report' ], 'permission_callback' => $jwt ],
        ] );
        register_rest_route( $ns, '/service/reports/(?P<id>\d+)', [
            [ 'methods' => 'GET', 'callback' => [ __CLASS__, 'get_report' ], 'permission_callback' => $jwt ],
            [ 'methods' => 'PUT', 'callback' => [ __CLASS__, 'save_report' ], 'permission_callback' => $jwt ],
        ] );
        register_rest_route( $ns, '/service/reports/(?P<id>\d+)/finalize', [
            'methods'             => 'POST',
            'callback'            => [ __CLASS__, 'finalize_report' ],
            'permission_callback' => $jwt,
        ] );

        register_rest_route( $ns, '/service/meta', [
            'methods'             => 'GET',
            'callback'            => [ __CLASS__, 'meta' ],
            'permission_callback' => $jwt,
        ] );

        register_rest_route( $ns, '/service/dashboard', [
            'methods'             => 'GET',
            'callback'            => [ __CLASS__, 'dashboard' ],
            'permission_callback' => $jwt,
        ] );
        register_rest_route( $ns, '/service/dispatch/queue', [
            'methods'             => 'GET',
            'callback'            => [ __CLASS__, 'dispatch_queue' ],
            'permission_callback' => $jwt,
        ] );
        register_rest_route( $ns, '/service/technicians', [
            'methods'             => 'GET',
            'callback'            => [ __CLASS__, 'list_technicians' ],
            'permission_callback' => $jwt,
        ] );
        register_rest_route( $ns, '/service/me/role', [
            'methods'             => 'GET',
            'callback'            => [ __CLASS__, 'my_service_role' ],
            'permission_callback' => $jwt,
        ] );
        register_rest_route( $ns, '/service/senior/team', [
            [ 'methods' => 'GET',  'callback' => [ __CLASS__, 'senior_team_list' ],  'permission_callback' => $jwt ],
            [ 'methods' => 'POST', 'callback' => [ __CLASS__, 'senior_team_add' ],  'permission_callback' => $jwt ],
        ] );
        register_rest_route( $ns, '/service/senior/team/(?P<technician_id>\d+)', [
            'methods'             => 'DELETE',
            'callback'            => [ __CLASS__, 'senior_team_remove' ],
            'permission_callback' => $jwt,
        ] );
        register_rest_route( $ns, '/service/senior/dashboard', [
            'methods'             => 'GET',
            'callback'            => [ __CLASS__, 'senior_dashboard' ],
            'permission_callback' => $jwt,
        ] );
        register_rest_route( $ns, '/service/technician/my-queue', [
            'methods'             => 'GET',
            'callback'            => [ __CLASS__, 'technician_my_queue' ],
            'permission_callback' => $jwt,
        ] );
        register_rest_route( $ns, '/service/parts/search', [
            'methods'             => 'GET',
            'callback'            => [ __CLASS__, 'search_parts' ],
            'permission_callback' => $jwt,
        ] );
        register_rest_route( $ns, '/service/reports/(?P<id>\d+)/html', [
            'methods'             => 'GET',
            'callback'            => [ __CLASS__, 'report_html' ],
            'permission_callback' => $jwt,
        ] );
        register_rest_route( $ns, '/service/pm/contracts', [
            'methods'             => 'GET',
            'callback'            => [ __CLASS__, 'list_pm_contracts' ],
            'permission_callback' => $jwt,
        ] );
        register_rest_route( $ns, '/service/pm/contracts/(?P<id>\d+)', [
            'methods'             => 'PUT',
            'callback'            => [ __CLASS__, 'update_pm_contract' ],
            'permission_callback' => $jwt,
        ] );
        register_rest_route( $ns, '/service/pm/run-reminders', [
            'methods'             => 'POST',
            'callback'            => [ __CLASS__, 'run_pm_reminders' ],
            'permission_callback' => $jwt,
        ] );
        register_rest_route( $ns, '/service/devices/(?P<id>\d+)/cost-chart', [
            'methods'             => 'GET',
            'callback'            => [ __CLASS__, 'device_cost_chart' ],
            'permission_callback' => $jwt,
        ] );
        register_rest_route( $ns, '/service/public/pay', [
            'methods'             => 'POST',
            'callback'            => [ __CLASS__, 'public_pay_request' ],
            'permission_callback' => $pub,
        ] );
        register_rest_route( $ns, '/service/public/pay/verify', [
            'methods'             => 'GET',
            'callback'            => [ __CLASS__, 'public_pay_verify' ],
            'permission_callback' => $pub,
        ] );
        register_rest_route( $ns, '/service/visitor/wizard-config', [
            'methods'             => 'GET',
            'callback'            => [ __CLASS__, 'visitor_wizard_config' ],
            'permission_callback' => $pub,
        ] );
        register_rest_route( $ns, '/service/visitor/analyze', [
            'methods'             => 'POST',
            'callback'            => [ __CLASS__, 'visitor_analyze' ],
            'permission_callback' => $pub,
        ] );
        register_rest_route( $ns, '/service/visitor/assist', [
            'methods'             => 'POST',
            'callback'            => [ __CLASS__, 'visitor_assist' ],
            'permission_callback' => $pub,
        ] );
        register_rest_route( $ns, '/service/visitor/parts-quote', [
            'methods'             => 'POST',
            'callback'            => [ __CLASS__, 'visitor_parts_quote' ],
            'permission_callback' => $pub,
        ] );
        register_rest_route( $ns, '/service/visitor/approve-cost', [
            'methods'             => 'POST',
            'callback'            => [ __CLASS__, 'visitor_approve_cost' ],
            'permission_callback' => $pub,
        ] );
        register_rest_route( $ns, '/service/visitor/review', [
            'methods'             => 'POST',
            'callback'            => [ __CLASS__, 'visitor_submit_review' ],
            'permission_callback' => $pub,
        ] );
        register_rest_route( $ns, '/service/customer/devices', [
            'methods'             => 'GET',
            'callback'            => [ __CLASS__, 'customer_devices_dashboard' ],
            'permission_callback' => $pub,
        ] );
        register_rest_route( $ns, '/service/pm/contracts/(?P<id>\d+)/visit', [
            'methods'             => 'POST',
            'callback'            => [ __CLASS__, 'pm_record_visit' ],
            'permission_callback' => $jwt,
        ] );
        register_rest_route( $ns, '/service/reports/(?P<id>\d+)/request-cost-approval', [
            'methods'             => 'POST',
            'callback'            => [ __CLASS__, 'request_report_cost_approval' ],
            'permission_callback' => $jwt,
        ] );
        register_rest_route( $ns, '/service/reports/(?P<id>\d+)/convert-repair-quote', [
            'methods'             => 'POST',
            'callback'            => [ __CLASS__, 'convert_visit_to_repair_quote' ],
            'permission_callback' => $jwt,
        ] );
        register_rest_route( $ns, '/service/reports/(?P<id>\d+)/senior-review', [
            'methods'             => 'POST',
            'callback'            => [ __CLASS__, 'senior_review_report' ],
            'permission_callback' => $jwt,
        ] );
        register_rest_route( $ns, '/service/reports/pending-review', [
            'methods'             => 'GET',
            'callback'            => [ __CLASS__, 'list_reports_pending_review' ],
            'permission_callback' => $jwt,
        ] );
        register_rest_route( $ns, '/service/parts/orders', [
            [ 'methods' => 'GET', 'callback' => [ __CLASS__, 'list_parts_orders' ], 'permission_callback' => $jwt ],
        ] );
        register_rest_route( $ns, '/service/parts/orders/(?P<id>\d+)', [
            [ 'methods' => 'PUT', 'callback' => [ __CLASS__, 'update_parts_order' ], 'permission_callback' => $jwt ],
        ] );
        register_rest_route( $ns, '/service/equipment/inquiries', [
            [ 'methods' => 'GET', 'callback' => [ __CLASS__, 'list_equipment_inquiries' ], 'permission_callback' => $jwt ],
        ] );
        register_rest_route( $ns, '/service/equipment/inquiries/(?P<id>\d+)', [
            [ 'methods' => 'PUT', 'callback' => [ __CLASS__, 'update_equipment_inquiry' ], 'permission_callback' => $jwt ],
        ] );
    }

    /* ══════════════════════════════════════════════
       VISITOR
    ══════════════════════════════════════════════ */

    public static function visitor_create_request( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;
        require_once ACC_PATH . 'includes/helpers/svc-algorithm.php';

        $session_key = sanitize_text_field( $req->get_param( 'session_key' ) ?? '' );
        $name        = sanitize_text_field( $req->get_param( 'name' ) ?? 'بازدیدکننده' );
        $phone       = sanitize_text_field( $req->get_param( 'phone' ) ?? '' );
        $email       = sanitize_email( $req->get_param( 'email' ) ?? '' );
        $service_type = sanitize_key( $req->get_param( 'service_type' ) ?? 'repair' );

        if ( empty( $session_key ) ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'session_key الزامی است' ], 400 );
        }
        if ( ! isset( self::$SERVICE_TYPES[ $service_type ] ) ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'نوع خدمت نامعتبر' ], 400 );
        }
        if ( empty( $phone ) && empty( $email ) ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'موبایل یا ایمیل الزامی است' ], 400 );
        }

        $payload = [
            'service_type'   => $service_type,
            'name'           => $name,
            'phone'          => $phone,
            'site_label'     => sanitize_text_field( $req->get_param( 'site_label' ) ?? '' ),
            'device_type'    => sanitize_text_field( $req->get_param( 'device_type' ) ?? '' ),
            'brand'          => sanitize_text_field( $req->get_param( 'brand' ) ?? '' ),
            'model'          => sanitize_text_field( $req->get_param( 'model' ) ?? '' ),
            'capacity'       => sanitize_text_field( $req->get_param( 'capacity' ) ?? '' ),
            'refrigerant'    => sanitize_text_field( $req->get_param( 'refrigerant' ) ?? '' ),
            'complaint_text' => sanitize_textarea_field( $req->get_param( 'complaint_text' ) ?? '' ),
            'priority'       => sanitize_key( $req->get_param( 'priority' ) ?? 'normal' ),
            'error_code'     => sanitize_text_field( $req->get_param( 'error_code' ) ?? '' ),
            'ambient_temp'   => $req->get_param( 'ambient_temp' ),
            'suction_pressure' => sanitize_text_field( (string) ( $req->get_param( 'suction_pressure' ) ?? '' ) ),
            'discharge_pressure' => sanitize_text_field( (string) ( $req->get_param( 'discharge_pressure' ) ?? '' ) ),
            'operating_hours'=> (int) ( $req->get_param( 'operating_hours' ) ?? 0 ),
            'pm_plan'        => sanitize_key( $req->get_param( 'pm_plan' ) ?? '' ),
            'part_number'    => sanitize_text_field( $req->get_param( 'part_number' ) ?? '' ),
            'part_qty'       => (int) ( $req->get_param( 'part_qty' ) ?? 1 ),
            'project_city'   => sanitize_text_field( $req->get_param( 'project_city' ) ?? '' ),
            'application'    => sanitize_text_field( $req->get_param( 'application' ) ?? '' ),
            'contract_no'    => sanitize_text_field( $req->get_param( 'contract_no' ) ?? '' ),
        ];

        if ( file_exists( ACC_PATH . 'includes/helpers/svc-rewards.php' ) ) {
            require_once ACC_PATH . 'includes/helpers/svc-rewards.php';
            if ( Svc_Rewards::has_benefit( $phone, 'priority_dispatch' ) ) {
                $payload['priority'] = 'urgent';
            }
        }

        $validation = Svc_Algorithm::validate_payload( $service_type, $payload );
        if ( ! $validation['complete'] ) {
            return new WP_REST_Response( [
                'success' => false,
                'message' => 'اطلاعات ناقص است',
                'missing' => $validation['missing'],
                'need_more_info' => true,
            ], 400 );
        }

        $consult_result = null;
        $cost_estimate = 0;
        if ( in_array( $service_type, [ 'phone_consult', 'online_consult', 'troubleshooting' ], true ) ) {
            $consult_result = Svc_Algorithm::analyze_consult( array_merge( $payload, [ 'service_type' => $service_type ] ) );
            $cost_estimate = (float) ( $consult_result['cost_estimate'] ?? 0 );
        } elseif ( $service_type === 'repair' ) {
            $cost_estimate = Svc_Algorithm::estimate_repair_cost( $payload );
        } elseif ( $service_type === 'parts_supply' ) {
            $consult_result = Svc_Algorithm::quote_parts( $payload );
            $cost_estimate = (float) ( $consult_result['price'] ?? 0 );
        }

        $data = [
            'session_key'    => $session_key,
            'customer_name'  => $name,
            'company'        => sanitize_text_field( $req->get_param( 'company' ) ?? '' ),
            'phone'          => $phone,
            'email'          => $email,
            'address'        => sanitize_textarea_field( $req->get_param( 'address' ) ?? '' ),
            'site_label'     => $payload['site_label'],
            'service_type'   => $service_type,
            'complaint_text' => $payload['complaint_text'],
            'complaint_tags' => wp_json_encode( array_map( 'sanitize_text_field', (array) ( $req->get_param( 'complaint_tags' ) ?? [] ) ) ),
            'device_type'    => $payload['device_type'],
            'brand'          => $payload['brand'],
            'model'          => $payload['model'],
            'serial_no'      => sanitize_text_field( $req->get_param( 'serial_no' ) ?? '' ),
            'capacity'       => $payload['capacity'],
            'refrigerant'    => $payload['refrigerant'],
            'install_location' => sanitize_text_field( $req->get_param( 'install_location' ) ?? '' ),
            'priority'       => in_array( $payload['priority'], [ 'urgent', 'normal' ], true ) ? $payload['priority'] : 'normal',
            'cost_estimate'  => $cost_estimate,
            'extra_data'     => Svc_Algorithm::build_extra_data( array_merge( $payload, [
                'consult_result' => $consult_result,
                'cost_estimate'  => $cost_estimate,
            ] ) ),
        ];

        $created = self::_create_request_with_chat( $data );
        if ( ! $created['success'] ) {
            return new WP_REST_Response( $created, 500 );
        }

        $req_id = (int) ( $created['data']['id'] ?? 0 );
        $code   = $created['data']['tracking_code'] ?? '';

        if ( $service_type === 'parts_supply' && is_array( $consult_result ) ) {
            self::_create_parts_order( $req_id, $code, $data, $consult_result );
        }
        if ( $service_type === 'equipment_purchase' ) {
            self::_create_equipment_inquiry( $req_id, $code, $data, $payload );
        }

        if ( file_exists( ACC_PATH . 'includes/helpers/svc-rewards.php' ) ) {
            require_once ACC_PATH . 'includes/helpers/svc-rewards.php';
            Svc_Rewards::on_visitor_request( $data, $req_id );
            if ( $service_type === 'parts_supply' && ! empty( $consult_result['in_stock'] ) ) {
                Svc_Rewards::on_customer_event( $phone, 'parts_purchase', [ 'order_value' => $cost_estimate, 'ref_id' => $req_id, 'dedupe_key' => 'parts_req_' . $req_id ] );
            }
            if ( $service_type === 'equipment_purchase' ) {
                Svc_Rewards::on_customer_event( $phone, 'equipment_purchase', [ 'order_value' => 0, 'ref_id' => $req_id, 'dedupe_key' => 'equip_req_' . $req_id ] );
            }
        }

        if ( file_exists( ACC_PATH . 'includes/helpers/svc-marketer.php' ) ) {
            require_once ACC_PATH . 'includes/helpers/svc-marketer.php';
            $mkt_code = strtoupper( sanitize_text_field( $req->get_param( 'marketer_code' ) ?? $req->get_param( 'mkt' ) ?? '' ) );
            if ( $mkt_code ) {
                $marketer = Svc_Marketer::get_by_code( $mkt_code );
                if ( $marketer ) {
                    $data['marketer_id'] = (int) $marketer['id'];
                    Svc_Marketer::attribute_by_code( $mkt_code, $phone, $name );
                }
            }
            Svc_Marketer::on_request_created( array_merge( $data, [
                'cost_estimate' => $cost_estimate,
            ] ), $req_id );
        }

        $created['data']['consult_result'] = $consult_result;
        $created['data']['cost_estimate']  = $cost_estimate;

        return new WP_REST_Response( [ 'success' => true, 'data' => $created['data'] ], 201 );
    }

    public static function visitor_wizard_config( WP_REST_Request $req ): WP_REST_Response {
        require_once ACC_PATH . 'includes/helpers/svc-algorithm.php';
        $type = sanitize_key( $req->get_param( 'service_type' ) ?? '' );
        return new WP_REST_Response( [
            'routes'  => Svc_Algorithm::entry_routes(),
            'fields'  => $type ? Svc_Algorithm::wizard_fields( $type ) : [],
            'service_types' => self::$SERVICE_TYPES,
        ] );
    }

    public static function visitor_analyze( WP_REST_Request $req ): WP_REST_Response {
        require_once ACC_PATH . 'includes/helpers/svc-algorithm.php';
        $payload = $req->get_json_params() ?: [];
        $type = sanitize_key( $payload['service_type'] ?? 'phone_consult' );
        $result = Svc_Algorithm::analyze_consult( array_merge( $payload, [ 'service_type' => $type ] ) );
        if ( ! empty( $payload['brand'] ) ) {
            $result['suggested_parts'] = Svc_Algorithm::suggest_parts( $payload['brand'], $payload['model'] ?? '' );
        }
        return new WP_REST_Response( [ 'success' => true, 'data' => $result ] );
    }

    /** دستیار HVAC — تشخیص نوع خدمت از متن آزاد */
    public static function visitor_assist( WP_REST_Request $req ): WP_REST_Response {
        require_once ACC_PATH . 'includes/helpers/svc-algorithm.php';
        $payload = $req->get_json_params() ?: [];
        $text = sanitize_textarea_field( $payload['text'] ?? $payload['message'] ?? '' );
        if ( $text === '' ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'متن مشکل را وارد کنید' ], 400 );
        }
        $intent = Svc_Algorithm::detect_service_intent( $text );
        $consult = null;
        if ( in_array( $intent['service_type'], [ 'phone_consult', 'online_consult', 'troubleshooting', 'repair' ], true ) ) {
            $consult = Svc_Algorithm::analyze_consult( array_merge( $payload, [
                'service_type'   => $intent['service_type'] === 'repair' ? 'repair' : 'phone_consult',
                'complaint_text' => $text,
                'error_code'     => $payload['error_code'] ?? '',
            ] ) );
        }
        return new WP_REST_Response( [
            'success' => true,
            'data'    => array_merge( $intent, [
                'analysis'       => $consult,
                'register_hint'  => 'می‌توانید همین الان از ویجت پشتیبانی مسیر «'
                    . ( $intent['service_label'] ?? '' ) . '» را ثبت کنید.',
            ] ),
        ] );
    }

    public static function visitor_parts_quote( WP_REST_Request $req ): WP_REST_Response {
        require_once ACC_PATH . 'includes/helpers/svc-algorithm.php';
        $payload = $req->get_json_params() ?: [];
        $quote = Svc_Algorithm::quote_parts( $payload );
        if ( ! empty( $payload['brand'] ) ) {
            $quote['suggested_parts'] = Svc_Algorithm::suggest_parts( $payload['brand'], $payload['model'] ?? '' );
        }
        return new WP_REST_Response( [ 'success' => true, 'data' => $quote ] );
    }

    public static function visitor_approve_cost( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;
        $code = strtoupper( sanitize_text_field( $req->get_param( 'tracking_code' ) ?? '' ) );
        $approve = (bool) ( $req->get_param( 'approved' ) ?? true );
        $row = self::_get_request_by_code( $code );
        if ( ! $row ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'درخواست یافت نشد' ], 404 );
        }
        $wpdb->update( "{$wpdb->prefix}svc_requests", [
            'cost_approved'    => $approve ? 1 : 0,
            'cost_approved_at' => current_time( 'mysql' ),
            'status'           => $approve ? 'assigned' : $row['status'],
            'updated_at'       => current_time( 'mysql' ),
        ], [ 'id' => (int) $row['id'] ] );

        $report = $wpdb->get_row( $wpdb->prepare(
            "SELECT id FROM {$wpdb->prefix}svc_reports WHERE request_id=%d ORDER BY id DESC LIMIT 1",
            (int) $row['id']
        ), ARRAY_A );
        if ( $report ) {
            $wpdb->update( "{$wpdb->prefix}svc_reports", [
                'cost_approval_status' => $approve ? 'approved' : 'rejected',
                'cost_approval_at'     => current_time( 'mysql' ),
            ], [ 'id' => (int) $report['id'] ] );
        }

        return new WP_REST_Response( [ 'success' => true, 'approved' => $approve ] );
    }

    public static function visitor_submit_review( WP_REST_Request $req ): WP_REST_Response {
        $phone = sanitize_text_field( $req->get_param( 'phone' ) ?? '' );
        $code  = strtoupper( sanitize_text_field( $req->get_param( 'tracking_code' ) ?? '' ) );
        $rating = max( 1, min( 5, (int) ( $req->get_param( 'rating' ) ?? 5 ) ) );

        if ( file_exists( ACC_PATH . 'includes/helpers/svc-rewards.php' ) ) {
            require_once ACC_PATH . 'includes/helpers/svc-rewards.php';
            $acct = Svc_Rewards::get_or_create_account( 'customer', [ 'phone' => $phone ] );
            Svc_Rewards::earn( (int) $acct['id'], 'review', [
                'dedupe_key' => 'review_' . $code,
                'quality_mult' => $rating >= 4 ? 1.5 : 1.0,
            ] );
            if ( $rating >= 4 ) {
                $row = self::_get_request_by_code( $code );
                if ( $row ) {
                    $tech_id = (int) ( $row['assigned_technician_id'] ?? 0 );
                    if ( $tech_id ) {
                        $tacct = Svc_Rewards::get_or_create_account( 'technician', [ 'user_id' => $tech_id ] );
                        Svc_Rewards::earn( (int) $tacct['id'], 'high_satisfaction', [
                            'dedupe_key' => 'sat_' . $code,
                            'quality_mult' => 1.2,
                        ] );
                    }
                }
            }
        }
        return new WP_REST_Response( [ 'success' => true, 'message' => 'نظر ثبت شد — امتیاز پاداش اضافه شد' ] );
    }

    public static function customer_devices_dashboard( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;
        $phone = sanitize_text_field( $req->get_param( 'phone' ) ?? '' );
        if ( ! $phone ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'موبایل لازم است' ], 400 );
        }
        require_once ACC_PATH . 'includes/helpers/svc-algorithm.php';
        $devices = $wpdb->get_results( $wpdb->prepare(
            "SELECT * FROM {$wpdb->prefix}svc_devices WHERE phone=%s ORDER BY updated_at DESC LIMIT 20",
            $phone
        ), ARRAY_A ) ?: [];
        foreach ( $devices as &$d ) {
            $d['alerts'] = Svc_Algorithm::consumption_alerts( (int) $d['id'] );
        }
        return new WP_REST_Response( [ 'success' => true, 'data' => $devices ] );
    }

    public static function pm_record_visit( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;
        $id = (int) $req->get_param( 'id' );
        $visit = $req->get_param( 'visit_result' );
        if ( is_array( $visit ) ) {
            $visit = wp_json_encode( $visit, JSON_UNESCAPED_UNICODE );
        }
        $hours = (int) ( $req->get_param( 'operating_hours' ) ?? 0 );
        $wpdb->update( "{$wpdb->prefix}svc_pm_contracts", [
            'visit_result'    => $visit,
            'operating_hours' => $hours,
            'updated_at'      => current_time( 'mysql' ),
        ], [ 'id' => $id ] );
        if ( $hours > 0 ) {
            $pm = $wpdb->get_row( $wpdb->prepare( "SELECT device_id FROM {$wpdb->prefix}svc_pm_contracts WHERE id=%d", $id ), ARRAY_A );
            if ( ! empty( $pm['device_id'] ) ) {
                $wpdb->update( "{$wpdb->prefix}svc_devices", [
                    'operating_hours' => $hours,
                    'updated_at'      => current_time( 'mysql' ),
                ], [ 'id' => (int) $pm['device_id'] ] );
            }
        }
        return new WP_REST_Response( [ 'success' => true ] );
    }

    public static function visitor_list_cases( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;

        $session_key = sanitize_text_field( $req->get_param( 'session_key' ) ?? '' );
        $phone       = sanitize_text_field( $req->get_param( 'phone' ) ?? '' );
        $code        = strtoupper( sanitize_text_field( $req->get_param( 'tracking_code' ) ?? '' ) );

        if ( empty( $session_key ) && empty( $phone ) && empty( $code ) ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'session_key یا phone یا tracking_code لازم است' ], 400 );
        }

        $table = "{$wpdb->prefix}svc_requests";
        $wheres = [];
        $params = [];

        if ( $code ) {
            $wheres[] = 'tracking_code = %s';
            $params[] = $code;
        } else {
            if ( $session_key ) {
                $wheres[] = 'session_key = %s';
                $params[] = $session_key;
            }
            if ( $phone ) {
                $wheres[] = 'phone = %s';
                $params[] = $phone;
            }
        }

        $sql = "SELECT id, request_no, tracking_code, service_type, status, site_label,
                       customer_name, complaint_text, conversation_id, created_at, updated_at
                FROM {$table}
                WHERE " . implode( ' OR ', $wheres ) . '
                ORDER BY created_at DESC
                LIMIT 50';

        $rows = $wpdb->get_results( $wpdb->prepare( $sql, ...$params ), ARRAY_A ) ?: [];

        foreach ( $rows as &$r ) {
            $r['service_type_label'] = self::$SERVICE_TYPES[ $r['service_type'] ] ?? $r['service_type'];
            $r['status_label']       = self::$REQUEST_STATUS[ $r['status'] ] ?? $r['status'];
        }

        return new WP_REST_Response( [ 'success' => true, 'data' => $rows ] );
    }

    public static function visitor_track( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;
        require_once ACC_PATH . 'includes/helpers/svc-algorithm.php';

        $code = strtoupper( sanitize_text_field( $req->get_param( 'code' ) ?? '' ) );
        $row  = self::_get_request_by_code( $code );
        if ( ! $row ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'کد پیگیری یافت نشد' ], 404 );
        }

        $timeline = self::_request_timeline( (int) $row['id'] );
        $row['service_type_label'] = self::$SERVICE_TYPES[ $row['service_type'] ] ?? $row['service_type'];
        $row['status_label']       = self::$REQUEST_STATUS[ $row['status'] ] ?? $row['status'];
        $row['timeline']           = $timeline;
        $row['extra']              = Svc_Algorithm::decode_extra( $row['extra_data'] ?? null );
        $row['priority_label']     = ( $row['priority'] ?? '' ) === 'urgent' ? 'فوری' : 'عادی';

        $pending_report = $wpdb->get_row( $wpdb->prepare(
            "SELECT cost_approval_status FROM {$wpdb->prefix}svc_reports
             WHERE request_id=%d AND cost_approval_status='pending' ORDER BY id DESC LIMIT 1",
            (int) $row['id']
        ), ARRAY_A );
        if ( (float) ( $row['cost_estimate'] ?? 0 ) > 0 || $pending_report ) {
            $row['cost_approval'] = [
                'estimate'    => (float) ( $row['cost_estimate'] ?? 0 ),
                'approved'    => (bool) ( $row['cost_approved'] ?? false ),
                'pending'     => (bool) $pending_report,
                'can_approve' => $pending_report && ! ( $row['cost_approved'] ?? false ),
            ];
        }

        $report = $wpdb->get_row( $wpdb->prepare(
            "SELECT id, report_no, balance, total, paid, public_token
             FROM {$wpdb->prefix}svc_reports
             WHERE request_id = %d AND status = 'final' ORDER BY finalized_at DESC LIMIT 1",
            (int) $row['id']
        ), ARRAY_A );
        if ( $report && (float) $report['balance'] > 0 ) {
            require_once ACC_PATH . 'includes/helpers/service-report-render.php';
            $row['payment'] = [
                'report_no'  => $report['report_no'],
                'balance'    => (float) $report['balance'],
                'public_token' => $report['public_token'],
                'pay_url'    => self::_pay_page_url( $report['public_token'] ),
            ];
        }

        return new WP_REST_Response( [ 'success' => true, 'data' => $row ] );
    }

    public static function public_report( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;

        $token = sanitize_text_field( $req->get_param( 'token' ) ?? '' );
        $row   = $wpdb->get_row( $wpdb->prepare(
            "SELECT * FROM {$wpdb->prefix}svc_reports WHERE public_token = %s AND status = 'final' LIMIT 1",
            $token
        ), ARRAY_A );

        if ( ! $row ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'گزارش یافت نشد' ], 404 );
        }

        self::_decode_report_json( $row );
        return new WP_REST_Response( [ 'success' => true, 'data' => $row ] );
    }

    /* ══════════════════════════════════════════════
       ERP — REQUESTS
    ══════════════════════════════════════════════ */

    public static function list_requests( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;

        $status = sanitize_key( $req->get_param( 'status' ) ?? '' );
        $search = sanitize_text_field( $req->get_param( 'search' ) ?? '' );
        $limit  = min( 100, max( 1, (int) ( $req->get_param( 'limit' ) ?? 50 ) ) );

        $wheres = [ '1=1' ];
        $params = [];

        if ( $status ) {
            $wheres[] = 'status = %s';
            $params[] = $status;
        }
        if ( $search ) {
            $wheres[] = '(request_no LIKE %s OR tracking_code LIKE %s OR customer_name LIKE %s OR phone LIKE %s OR site_label LIKE %s)';
            $like     = '%' . $wpdb->esc_like( $search ) . '%';
            array_push( $params, $like, $like, $like, $like, $like );
        }

        self::_ensure_teams();
        self::_append_request_scope( $wheres, $params, self::_current_uid() );

        $sql = "SELECT r.*, u.display_name AS technician_name
                FROM {$wpdb->prefix}svc_requests r
                LEFT JOIN {$wpdb->users} u ON u.ID = r.assigned_technician_id
                WHERE " . implode( ' AND ', $wheres ) . "
                ORDER BY CASE WHEN r.priority='urgent' THEN 0 ELSE 1 END,
                         r.created_at DESC LIMIT {$limit}";

        $rows = $params
            ? ( $wpdb->get_results( $wpdb->prepare( $sql, ...$params ), ARRAY_A ) ?: [] )
            : ( $wpdb->get_results( $sql, ARRAY_A ) ?: [] );

        foreach ( $rows as &$r ) {
            self::_enrich_request_row( $r );
        }

        return new WP_REST_Response( [ 'data' => $rows ] );
    }

    public static function get_request( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;
        $id  = (int) $req->get_param( 'id' );
        $row = self::_get_request( $id );
        if ( ! $row ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'یافت نشد' ], 404 );
        }
        $row['timeline'] = self::_request_timeline( $id );

        $reports = $wpdb->get_results( $wpdb->prepare(
            "SELECT id, report_no, status, total, balance, paid, public_token, senior_review_status, finalized_at
             FROM {$wpdb->prefix}svc_reports WHERE request_id = %d ORDER BY created_at ASC",
            $id
        ), ARRAY_A ) ?: [];
        require_once ACC_PATH . 'includes/helpers/service-report-render.php';
        foreach ( $reports as &$rp ) {
            if ( ! empty( $rp['public_token'] ) ) {
                $rp['public_url'] = Service_Report_Render::public_url( $rp['public_token'] );
            }
        }
        $row['reports'] = $reports;

        $parts = $wpdb->get_row( $wpdb->prepare(
            "SELECT * FROM {$wpdb->prefix}svc_parts_orders WHERE request_id = %d LIMIT 1", $id
        ), ARRAY_A );
        if ( $parts ) {
            $row['parts_order'] = $parts;
        }

        $equip = $wpdb->get_row( $wpdb->prepare(
            "SELECT * FROM {$wpdb->prefix}svc_equipment_inquiries WHERE request_id = %d LIMIT 1", $id
        ), ARRAY_A );
        if ( $equip ) {
            $equip['quote_options'] = json_decode( $equip['quote_options'] ?? '[]', true ) ?: [];
            $row['equipment_inquiry'] = $equip;
        }

        return new WP_REST_Response( [ 'data' => $row ] );
    }

    public static function create_request( WP_REST_Request $req ): WP_REST_Response {
        $data = self::_parse_request_body( $req );
        $data['session_key'] = 'erp_' . get_current_user_id();
        $created = self::_create_request_with_chat( $data, false );
        if ( ! $created['success'] ) {
            return new WP_REST_Response( $created, 500 );
        }
        return new WP_REST_Response( [ 'success' => true, 'data' => $created['data'] ], 201 );
    }

    public static function update_request( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;

        $id  = (int) $req->get_param( 'id' );
        $row = self::_get_request( $id );
        if ( ! $row ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'یافت نشد' ], 404 );
        }

        $fields = [
            'status', 'assigned_technician_id', 'site_label', 'address', 'complaint_text',
            'device_type', 'brand', 'model', 'serial_no', 'capacity', 'refrigerant', 'install_location',
            'scheduled_at', 'dispatch_notes',
        ];
        $update = [];
        foreach ( $fields as $f ) {
            if ( null !== $req->get_param( $f ) ) {
                if ( in_array( $f, [ 'assigned_technician_id' ], true ) ) {
                    $update[ $f ] = (int) $req->get_param( $f ) ?: null;
                } elseif ( $f === 'status' ) {
                    $update[ $f ] = sanitize_key( $req->get_param( $f ) );
                } else {
                    $update[ $f ] = sanitize_textarea_field( (string) $req->get_param( $f ) );
                }
            }
        }
        if ( isset( $update['status'] ) && ! isset( self::$REQUEST_STATUS[ $update['status'] ] ) ) {
            unset( $update['status'] );
        }

        if ( $update ) {
            $update['updated_at'] = current_time( 'mysql' );
            $wpdb->update( "{$wpdb->prefix}svc_requests", $update, [ 'id' => $id ] );
        }

        return new WP_REST_Response( [ 'success' => true, 'data' => self::_get_request( $id ) ] );
    }

    public static function assign_request( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;

        $id      = (int) $req->get_param( 'id' );
        $tech_id = (int) ( $req->get_param( 'technician_id' ) ?? 0 );
        $status  = sanitize_key( $req->get_param( 'status' ) ?? '' );

        self::_ensure_teams();
        if ( $tech_id && ! Svc_Teams::can_access_technician( self::_current_uid(), $tech_id ) ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'تخصیص به این کارشناس مجاز نیست' ], 403 );
        }

        $update = [
            'assigned_technician_id' => $tech_id ?: null,
            'status'                 => $status && isset( self::$REQUEST_STATUS[ $status ] )
                ? $status
                : ( $tech_id ? 'assigned' : 'submitted' ),
            'updated_at'             => current_time( 'mysql' ),
        ];

        if ( null !== $req->get_param( 'scheduled_at' ) ) {
            $update['scheduled_at'] = sanitize_text_field( $req->get_param( 'scheduled_at' ) ) ?: null;
        }
        if ( null !== $req->get_param( 'dispatch_notes' ) ) {
            $update['dispatch_notes'] = sanitize_textarea_field( $req->get_param( 'dispatch_notes' ) );
        }

        $wpdb->update( "{$wpdb->prefix}svc_requests", $update, [ 'id' => $id ] );

        self::_sync_chat_agent_on_assign( $id, $tech_id );

        if ( $tech_id && file_exists( ACC_PATH . 'includes/helpers/svc-rewards.php' ) ) {
            require_once ACC_PATH . 'includes/helpers/svc-rewards.php';
            Svc_Rewards::on_tech_fast_response( $tech_id, $id );
        }

        return new WP_REST_Response( [ 'success' => true, 'data' => self::_get_request( $id ) ] );
    }

    private static function _sync_chat_agent_on_assign( int $request_id, int $tech_id ): void {
        global $wpdb;
        if ( ! $tech_id ) return;

        $row = self::_get_request( $request_id );
        if ( ! $row || empty( $row['conversation_id'] ) ) return;

        $conv_id = (int) $row['conversation_id'];
        $wpdb->update( "{$wpdb->prefix}chat_conversations", [
            'agent_id'   => $tech_id,
            'updated_at' => current_time( 'mysql' ),
        ], [ 'id' => $conv_id ] );

        $tech_name = $wpdb->get_var( $wpdb->prepare(
            "SELECT display_name FROM {$wpdb->users} WHERE ID = %d", $tech_id
        ) ) ?: 'کارشناس';

        $wpdb->insert( "{$wpdb->prefix}chat_messages", [
            'conversation_id' => $conv_id,
            'sender_type'     => 'system',
            'sender_id'       => '0',
            'message'         => "👷 کارشناس {$tech_name} به این پرونده تخصیص یافت.",
            'created_at'      => current_time( 'mysql' ),
        ] );
    }

    private static function _current_uid(): int {
        $uid = (int) get_current_user_id();
        if ( ! $uid && isset( $GLOBALS['acc_jwt_user_id'] ) ) {
            $uid = (int) $GLOBALS['acc_jwt_user_id'];
        }
        return $uid;
    }

    private static function _ensure_teams(): void {
        require_once ACC_PATH . 'includes/helpers/svc-teams.php';
    }

    /** فیلتر درخواست‌ها بر اساس نقش کارشناس/ارشد */
    private static function _append_request_scope( array &$wheres, array &$params, int $uid ): void {
        self::_ensure_teams();
        if ( Svc_Teams::is_admin( $uid ) ) {
            return;
        }
        if ( Svc_Teams::is_technician( $uid ) ) {
            $wheres[] = 'r.assigned_technician_id = %d';
            $params[] = $uid;
            return;
        }
        if ( Svc_Teams::is_senior( $uid ) ) {
            $team = Svc_Teams::team_technician_ids( $uid );
            if ( ! $team ) {
                $wheres[] = '1=0';
                return;
            }
            $ph = implode( ',', array_fill( 0, count( $team ), '%d' ) );
            $wheres[] = "(r.assigned_technician_id IN ({$ph}) OR r.assigned_technician_id IS NULL OR r.assigned_technician_id = 0)";
            array_push( $params, ...$team );
        }
    }

    /* ══════════════════════════════════════════════
       ERP — DEVICES
    ══════════════════════════════════════════════ */

    public static function list_devices( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;

        $search = sanitize_text_field( $req->get_param( 'search' ) ?? '' );
        $sql    = "SELECT * FROM {$wpdb->prefix}svc_devices";
        $params = [];

        if ( $search ) {
            $like = '%' . $wpdb->esc_like( $search ) . '%';
            $sql .= ' WHERE serial_no LIKE %s OR brand LIKE %s OR model LIKE %s OR site_label LIKE %s';
            $params = [ $like, $like, $like, $like ];
        }
        $sql .= ' ORDER BY updated_at DESC LIMIT 100';

        $rows = $params
            ? ( $wpdb->get_results( $wpdb->prepare( $sql, ...$params ), ARRAY_A ) ?: [] )
            : ( $wpdb->get_results( $sql, ARRAY_A ) ?: [] );

        return new WP_REST_Response( [ 'data' => $rows ] );
    }

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

    public static function create_device( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;
        $data = self::_parse_device_body( $req );
        $data['created_at'] = current_time( 'mysql' );
        $data['updated_at'] = current_time( 'mysql' );
        $wpdb->insert( "{$wpdb->prefix}svc_devices", $data );
        $device_id = (int) $wpdb->insert_id;
        if ( $device_id && ! empty( $data['phone'] ) && file_exists( ACC_PATH . 'includes/helpers/svc-rewards.php' ) ) {
            require_once ACC_PATH . 'includes/helpers/svc-rewards.php';
            if ( ! empty( $data['device_type'] ) && ! empty( $data['brand'] ) ) {
                Svc_Rewards::on_device_profile_complete( $data['phone'], $device_id );
            }
        }
        return new WP_REST_Response( [ 'success' => true, 'id' => $device_id ], 201 );
    }

    public static function update_device( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;
        $id   = (int) $req->get_param( 'id' );
        $data = self::_parse_device_body( $req );
        $data['updated_at'] = current_time( 'mysql' );
        $wpdb->update( "{$wpdb->prefix}svc_devices", $data, [ 'id' => $id ] );
        return new WP_REST_Response( [ 'success' => true ] );
    }

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

        $requests = $wpdb->get_results( $wpdb->prepare(
            "SELECT id, request_no, tracking_code, service_type, status, created_at
             FROM {$wpdb->prefix}svc_requests WHERE device_id = %d ORDER BY created_at DESC",
            $id
        ), ARRAY_A ) ?: [];

        $reports = $wpdb->get_results( $wpdb->prepare(
            "SELECT id, report_no, service_type, status, total, created_at
             FROM {$wpdb->prefix}svc_reports WHERE device_id = %d ORDER BY created_at DESC",
            $id
        ), ARRAY_A ) ?: [];

        return new WP_REST_Response( [
            'data' => [
                'requests' => $requests,
                'reports'  => $reports,
            ],
        ] );
    }

    /* ══════════════════════════════════════════════
       ERP — REPORTS (SRD)
    ══════════════════════════════════════════════ */

    public static function list_reports( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;
        self::_ensure_teams();
        $uid = self::_current_uid();

        $wheres = [ '1=1' ];
        $params = [];

        if ( Svc_Teams::is_technician( $uid ) && ! Svc_Teams::is_admin( $uid ) && ! Svc_Teams::is_senior( $uid ) ) {
            $wheres[] = '(r.created_by = %d OR r.request_id IN (
                SELECT id FROM ' . $wpdb->prefix . 'svc_requests WHERE assigned_technician_id = %d
            ))';
            array_push( $params, $uid, $uid );
        }

        $sql = "SELECT r.id, r.report_no, r.request_id, r.device_id, r.service_type, r.status,
                    r.customer_name, r.site_label, r.total, r.balance, r.created_at,
                    r.senior_review_status, r.acc_revenue_event_id,
                    e.workflow_state AS acc_workflow_state
             FROM {$wpdb->prefix}svc_reports r
             LEFT JOIN {$wpdb->prefix}ai_acc_events e ON e.id = r.acc_revenue_event_id
             WHERE " . implode( ' AND ', $wheres ) . "
             ORDER BY r.created_at DESC LIMIT 100";

        $rows = $params
            ? ( $wpdb->get_results( $wpdb->prepare( $sql, ...$params ), ARRAY_A ) ?: [] )
            : ( $wpdb->get_results( $sql, ARRAY_A ) ?: [] );

        return new WP_REST_Response( [ 'data' => $rows ] );
    }

    public static function get_report( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;
        $id  = (int) $req->get_param( 'id' );
        $row = $wpdb->get_row( $wpdb->prepare(
            "SELECT * FROM {$wpdb->prefix}svc_reports WHERE id = %d", $id
        ), ARRAY_A );
        if ( ! $row ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'یافت نشد' ], 404 );
        }
        self::_decode_report_json( $row );
        require_once ACC_PATH . 'includes/helpers/service-report-render.php';
        $row['public_url'] = ! empty( $row['public_token'] )
            ? Service_Report_Render::public_url( $row['public_token'] )
            : '';
        return new WP_REST_Response( [ 'data' => $row ] );
    }

    public static function create_report( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;

        $request_id = (int) ( $req->get_param( 'request_id' ) ?? 0 );
        $request    = $request_id ? self::_get_request( $request_id ) : null;

        $report_no = self::_generate_report_no();
        $token     = self::_generate_public_token();
        $tags      = $request['complaint_tags'] ?? '[]';
        if ( is_string( $tags ) ) {
            $tags = json_decode( $tags, true ) ?: [];
        }

        $snap = self::_device_snapshot_from_request( $request );
        if ( ! empty( $request['device_id'] ) ) {
            $dev = $wpdb->get_row( $wpdb->prepare(
                "SELECT * FROM {$wpdb->prefix}svc_devices WHERE id=%d", (int) $request['device_id']
            ), ARRAY_A );
            if ( $dev ) {
                $snap['operating_hours']   = (int) ( $dev['operating_hours'] ?? 0 );
                $snap['last_service_date'] = $dev['last_service_date'] ?? '';
            }
        }

        $data = [
            'report_no'      => $report_no,
            'request_id'     => $request_id ?: null,
            'device_id'      => $request['device_id'] ?? (int) ( $req->get_param( 'device_id' ) ?? 0 ) ?: null,
            'service_type'   => sanitize_key( $req->get_param( 'service_type' ) ?? ( $request['service_type'] ?? 'repair' ) ),
            'status'         => 'draft',
            'report_date'    => current_time( 'Y-m-d' ),
            'customer_name'  => sanitize_text_field( $req->get_param( 'customer_name' ) ?? ( $request['customer_name'] ?? '' ) ),
            'company'        => sanitize_text_field( $req->get_param( 'company' ) ?? ( $request['company'] ?? '' ) ),
            'phone'          => sanitize_text_field( $req->get_param( 'phone' ) ?? ( $request['phone'] ?? '' ) ),
            'email'          => sanitize_email( $req->get_param( 'email' ) ?? ( $request['email'] ?? '' ) ),
            'address'        => sanitize_textarea_field( $req->get_param( 'address' ) ?? ( $request['address'] ?? '' ) ),
            'site_label'     => sanitize_text_field( $req->get_param( 'site_label' ) ?? ( $request['site_label'] ?? '' ) ),
            'case_no'        => $request['request_no'] ?? '',
            'complaint_text' => sanitize_textarea_field( $req->get_param( 'complaint_text' ) ?? ( $request['complaint_text'] ?? '' ) ),
            'complaint_tags' => wp_json_encode( array_map( 'sanitize_text_field', $tags ) ),
            'device_snapshot'=> wp_json_encode( $snap ),
            'public_token'   => $token,
            'created_by'     => get_current_user_id() ?: 0,
            'created_at'     => current_time( 'mysql' ),
            'updated_at'     => current_time( 'mysql' ),
        ];

        $wpdb->insert( "{$wpdb->prefix}svc_reports", $data );
        $id = (int) $wpdb->insert_id;

        return new WP_REST_Response( [
            'success' => true,
            'data'    => [ 'id' => $id, 'report_no' => $report_no, 'public_token' => $token ],
        ], 201 );
    }

    public static function save_report( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;

        $id  = (int) $req->get_param( 'id' );
        $row = $wpdb->get_row( $wpdb->prepare(
            "SELECT * FROM {$wpdb->prefix}svc_reports WHERE id = %d", $id
        ), ARRAY_A );
        if ( ! $row ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'یافت نشد' ], 404 );
        }
        if ( $row['status'] === 'final' ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'گزارش نهایی شده و قابل ویرایش نیست' ], 403 );
        }

        $json_fields = [
            'technical_readings', 'service_lines', 'part_lines', 'material_lines',
            'extra_costs', 'recommendations', 'pm_offer', 'complaint_tags', 'device_snapshot',
            'photos_before', 'photos_after',
        ];
        $text_fields = [
            'service_type', 'customer_name', 'company', 'phone', 'email', 'address', 'site_label',
            'complaint_text', 'diagnosis_result', 'tech_signature', 'customer_signature', 'company_stamp',
        ];
        $num_fields = [
            'subtotal_services', 'subtotal_parts', 'subtotal_materials', 'subtotal_extra',
            'discount', 'tax', 'total', 'paid', 'balance',
        ];

        $update = [ 'updated_at' => current_time( 'mysql' ) ];

        foreach ( $text_fields as $f ) {
            if ( null !== $req->get_param( $f ) ) {
                $update[ $f ] = sanitize_textarea_field( (string) $req->get_param( $f ) );
            }
        }
        foreach ( $json_fields as $f ) {
            if ( null !== $req->get_param( $f ) ) {
                $val = $req->get_param( $f );
                $update[ $f ] = is_string( $val ) ? $val : wp_json_encode( $val );
            }
        }
        foreach ( $num_fields as $f ) {
            if ( null !== $req->get_param( $f ) ) {
                $update[ $f ] = (float) $req->get_param( $f );
            }
        }

        if ( null !== $req->get_param( 'report_date' ) ) {
            $update['report_date'] = sanitize_text_field( $req->get_param( 'report_date' ) );
        }

        self::_recalc_totals( $update );
        $wpdb->update( "{$wpdb->prefix}svc_reports", $update, [ 'id' => $id ] );

        $saved = $wpdb->get_row( $wpdb->prepare(
            "SELECT * FROM {$wpdb->prefix}svc_reports WHERE id = %d", $id
        ), ARRAY_A );
        self::_decode_report_json( $saved );

        return new WP_REST_Response( [ 'success' => true, 'data' => $saved ] );
    }

    public static function finalize_report( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;

        $id = (int) $req->get_param( 'id' );
        self::save_report( $req );

        $warehouse_id = (int) ( $req->get_param( 'warehouse_id' ) ?? get_option( 'acc_svc_default_warehouse', 0 ) );

        $draft = $wpdb->get_row( $wpdb->prepare(
            "SELECT * FROM {$wpdb->prefix}svc_reports WHERE id = %d", $id
        ), ARRAY_A );
        if ( $draft && file_exists( ACC_PATH . 'includes/helpers/svc-rewards.php' ) ) {
            require_once ACC_PATH . 'includes/helpers/svc-rewards.php';
            self::_decode_report_json( $draft );
            $disc_update = [ 'updated_at' => current_time( 'mysql' ) ];
            foreach ( [ 'subtotal_services', 'subtotal_parts', 'subtotal_materials', 'subtotal_extra', 'discount', 'tax', 'paid' ] as $f ) {
                if ( isset( $draft[ $f ] ) ) $disc_update[ $f ] = $draft[ $f ];
            }
            Svc_Rewards::apply_invoice_discounts( $draft['phone'] ?? '', $disc_update );
            self::_recalc_totals( $disc_update );
            $wpdb->update( "{$wpdb->prefix}svc_reports", $disc_update, [ 'id' => $id ] );
        }

        $wpdb->update( "{$wpdb->prefix}svc_reports", [
            'status'       => 'final',
            'finalized_at' => current_time( 'mysql' ),
            'updated_at'   => current_time( 'mysql' ),
        ], [ 'id' => $id ] );

        self::_ensure_teams();
        $uid = self::_current_uid();
        $review_status = 'none';
        if ( Svc_Teams::is_technician( $uid ) && ! Svc_Teams::is_senior( $uid ) && ! Svc_Teams::is_admin( $uid ) ) {
            $review_status = 'pending';
        } elseif ( Svc_Teams::is_senior( $uid ) || Svc_Teams::is_admin( $uid ) ) {
            $review_status = 'approved';
        }
        $wpdb->update( "{$wpdb->prefix}svc_reports", [
            'senior_review_status' => $review_status,
            'senior_reviewed_by'   => in_array( $review_status, [ 'approved' ], true ) ? $uid : null,
            'senior_reviewed_at'   => in_array( $review_status, [ 'approved' ], true ) ? current_time( 'mysql' ) : null,
        ], [ 'id' => $id ] );

        $row = $wpdb->get_row( $wpdb->prepare(
            "SELECT * FROM {$wpdb->prefix}svc_reports WHERE id = %d", $id
        ), ARRAY_A );
        self::_decode_report_json( $row );

        if ( ! empty( $row['request_id'] ) ) {
            $wpdb->update( "{$wpdb->prefix}svc_requests", [
                'status'     => 'completed',
                'updated_at' => current_time( 'mysql' ),
            ], [ 'id' => (int) $row['request_id'] ] );
        }

        if ( $warehouse_id > 0 && class_exists( 'InventoryIntegration' ) ) {
            InventoryIntegration::on_service_report_finalized( $id, $warehouse_id );
        }

        self::_maybe_create_pm_contract( $row );
        self::_maybe_create_pm_preinvoice( $row );
        self::_link_device_from_report( $row );

        if ( file_exists( ACC_PATH . 'includes/helpers/svc-notify.php' ) ) {
            require_once ACC_PATH . 'includes/helpers/svc-notify.php';
            Svc_Notify::send_report_finalized( $row );
            $wpdb->update( "{$wpdb->prefix}svc_reports", [
                'notified_at' => current_time( 'mysql' ),
            ], [ 'id' => $id ] );
        }

        if ( file_exists( ACC_PATH . 'includes/helpers/svc-rewards.php' ) ) {
            require_once ACC_PATH . 'includes/helpers/svc-rewards.php';
            Svc_Rewards::on_report_finalized( $row );
            Svc_Rewards::on_report_extras( $row );
        }

        if ( file_exists( ACC_PATH . 'includes/helpers/svc-marketer.php' ) ) {
            require_once ACC_PATH . 'includes/helpers/svc-marketer.php';
            Svc_Marketer::on_report_finalized( $row );
        }

        if ( file_exists( ACC_PATH . 'includes/helpers/svc-accounting.php' ) ) {
            require_once ACC_PATH . 'includes/helpers/svc-accounting.php';
            $acc = Svc_Accounting::on_report_finalized( $row );
            $row['acc_post'] = $acc;
        }

        require_once ACC_PATH . 'includes/helpers/service-report-render.php';
        $row['public_url'] = Service_Report_Render::public_url( $row['public_token'] ?? '' );

        return new WP_REST_Response( [ 'success' => true, 'data' => $row ] );
    }

    public static function dashboard( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;
        $pre = $wpdb->prefix;

        $open = (int) $wpdb->get_var(
            "SELECT COUNT(*) FROM {$pre}svc_requests WHERE status IN ('submitted','assigned','in_progress')"
        );
        $today = (int) $wpdb->get_var( $wpdb->prepare(
            "SELECT COUNT(*) FROM {$pre}svc_requests WHERE DATE(created_at) = %s", current_time( 'Y-m-d' )
        ) );
        $completed_month = (int) $wpdb->get_var( $wpdb->prepare(
            "SELECT COUNT(*) FROM {$pre}svc_requests WHERE status='completed' AND created_at >= %s",
            date( 'Y-m-01' )
        ) );
        $revenue_month = (float) $wpdb->get_var( $wpdb->prepare(
            "SELECT COALESCE(SUM(total),0) FROM {$pre}svc_reports WHERE status='final' AND finalized_at >= %s",
            date( 'Y-m-01' )
        ) );
        $draft_reports = (int) $wpdb->get_var(
            "SELECT COUNT(*) FROM {$pre}svc_reports WHERE status='draft'"
        );
        $pm_active = (int) $wpdb->get_var(
            "SELECT COUNT(*) FROM {$pre}svc_pm_contracts WHERE status='active'"
        );

        return new WP_REST_Response( [
            'data' => [
                'open_requests'    => $open,
                'today_requests'   => $today,
                'completed_month'  => $completed_month,
                'revenue_month'    => $revenue_month,
                'draft_reports'    => $draft_reports,
                'pm_contracts'     => $pm_active,
            ],
        ] );
    }

    public static function dispatch_queue( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;

        $status = sanitize_key( $req->get_param( 'status' ) ?? '' );
        $wheres = [ "r.status IN ('submitted','assigned','in_progress')" ];
        $params = [];

        if ( $status ) {
            $wheres = [ 'r.status = %s' ];
            $params[] = $status;
        }

        self::_ensure_teams();
        self::_append_request_scope( $wheres, $params, self::_current_uid() );

        $sql = "SELECT r.*, u.display_name AS technician_name
                FROM {$wpdb->prefix}svc_requests r
                LEFT JOIN {$wpdb->users} u ON u.ID = r.assigned_technician_id
                WHERE " . implode( ' AND ', $wheres ) . "
                ORDER BY
                    CASE WHEN r.priority='urgent' THEN 0 ELSE 1 END,
                    CASE r.status WHEN 'submitted' THEN 1 WHEN 'assigned' THEN 2 ELSE 3 END,
                    r.scheduled_at ASC, r.created_at ASC
                LIMIT 100";

        $rows = $params
            ? ( $wpdb->get_results( $wpdb->prepare( $sql, ...$params ), ARRAY_A ) ?: [] )
            : ( $wpdb->get_results( $sql, ARRAY_A ) ?: [] );

        foreach ( $rows as &$r ) {
            self::_enrich_request_row( $r );
        }

        return new WP_REST_Response( [ 'data' => $rows ] );
    }

    public static function request_report_cost_approval( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;
        $id = (int) $req->get_param( 'id' );
        $report = $wpdb->get_row( $wpdb->prepare(
            "SELECT * FROM {$wpdb->prefix}svc_reports WHERE id=%d", $id
        ), ARRAY_A );
        if ( ! $report ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'گزارش یافت نشد' ], 404 );
        }
        if ( $report['status'] === 'final' ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'گزارش نهایی شده' ], 403 );
        }
        $request_id = (int) ( $report['request_id'] ?? 0 );
        if ( ! $request_id ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'درخواست مرتبط نیست' ], 400 );
        }

        self::_decode_report_json( $report );
        $estimate = (float) ( $report['total'] ?? 0 );
        if ( $estimate <= 0 ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'مبلغ گزارش صفر است — ابتدا ردیف‌های مالی را تکمیل کنید' ], 400 );
        }

        $wpdb->update( "{$wpdb->prefix}svc_requests", [
            'cost_estimate'    => $estimate,
            'cost_approved'    => 0,
            'cost_approved_at' => null,
            'updated_at'       => current_time( 'mysql' ),
        ], [ 'id' => $request_id ] );

        $wpdb->update( "{$wpdb->prefix}svc_reports", [
            'cost_approval_status' => 'pending',
            'cost_approval_at'     => current_time( 'mysql' ),
            'updated_at'           => current_time( 'mysql' ),
        ], [ 'id' => $id ] );

        $code = (string) $wpdb->get_var( $wpdb->prepare(
            "SELECT tracking_code FROM {$wpdb->prefix}svc_requests WHERE id=%d", $request_id
        ) );

        $req_row = self::_get_request( $request_id );
        if ( $req_row && file_exists( ACC_PATH . 'includes/helpers/svc-notify.php' ) ) {
            require_once ACC_PATH . 'includes/helpers/svc-notify.php';
            Svc_Notify::send_cost_approval_request(
                $req_row['phone'] ?? '',
                $req_row['email'] ?? '',
                $code,
                $estimate
            );
        }

        return new WP_REST_Response( [
            'success' => true,
            'data'    => [
                'estimate'       => $estimate,
                'tracking_code'  => $code,
                'approval_status'=> 'pending',
            ],
        ] );
    }

    public static function list_parts_orders( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;
        $status = sanitize_key( $req->get_param( 'status' ) ?? '' );
        $wheres = [ '1=1' ];
        $params = [];
        if ( $status ) {
            $wheres[] = 'status = %s';
            $params[] = $status;
        }
        $sql = "SELECT * FROM {$wpdb->prefix}svc_parts_orders WHERE " . implode( ' AND ', $wheres ) . ' ORDER BY created_at DESC LIMIT 100';
        $rows = $params
            ? ( $wpdb->get_results( $wpdb->prepare( $sql, ...$params ), ARRAY_A ) ?: [] )
            : ( $wpdb->get_results( $sql, ARRAY_A ) ?: [] );
        return new WP_REST_Response( [ 'data' => $rows ] );
    }

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

        $old_status = $row['status'];
        $update = [ 'updated_at' => current_time( 'mysql' ) ];
        foreach ( [ 'status', 'notes', 'supplier_id', 'eta_days' ] as $f ) {
            if ( null !== $req->get_param( $f ) ) {
                $update[ $f ] = $f === 'supplier_id' || $f === 'eta_days'
                    ? (int) $req->get_param( $f )
                    : sanitize_text_field( (string) $req->get_param( $f ) );
            }
        }
        if ( null !== $req->get_param( 'quote_price' ) ) {
            $update['quote_price'] = (float) $req->get_param( 'quote_price' );
        }

        $phone = $row['phone'] ?? '';
        if ( file_exists( ACC_PATH . 'includes/helpers/svc-rewards.php' ) ) {
            require_once ACC_PATH . 'includes/helpers/svc-rewards.php';
            if ( Svc_Rewards::has_benefit( $phone, 'free_shipping' ) && ( $update['status'] ?? '' ) === 'ready' ) {
                $update['notes'] = trim( ( $update['notes'] ?? $row['notes'] ?? '' ) . ' [ارسال رایگان — جایزه پاداش]' );
                Svc_Rewards::consume_benefit( $phone, 'free_shipping' );
            }
        }

        $wpdb->update( "{$wpdb->prefix}svc_parts_orders", $update, [ 'id' => $id ] );
        $new_status = $update['status'] ?? $old_status;

        if ( $new_status === 'delivered' && $old_status !== 'delivered' ) {
            if ( file_exists( ACC_PATH . 'includes/helpers/svc-rewards.php' ) ) {
            require_once ACC_PATH . 'includes/helpers/svc-rewards.php';
            $eta = (int) ( $update['eta_days'] ?? $row['eta_days'] ?? 5 );
            Svc_Rewards::on_supplier_event( 'on_time_delivery', [
                'ref_id' => $id, 'dedupe_key' => 'deliver_' . $id,
                'quality_mult' => $eta <= 3 ? 1.3 : 1.0,
            ] );
            Svc_Rewards::on_supplier_event( 'high_quality', [
                'ref_id' => $id, 'dedupe_key' => 'quality_' . $id,
            ] );
            $price = (float) ( $update['quote_price'] ?? $row['quote_price'] ?? 0 );
            if ( $price > 0 ) {
                Svc_Rewards::on_supplier_event( 'competitive_price', [
                    'ref_id' => $id, 'dedupe_key' => 'price_' . $id, 'order_value' => $price,
                ] );
            }
            }
            if ( file_exists( ACC_PATH . 'includes/helpers/svc-accounting.php' ) ) {
                require_once ACC_PATH . 'includes/helpers/svc-accounting.php';
                $row['status'] = 'delivered';
                $row['quote_price'] = $update['quote_price'] ?? $row['quote_price'] ?? 0;
                Svc_Accounting::on_supplier_delivered( $row );
            }
        }
        if ( $new_status === 'shipped' && $old_status !== 'shipped' && file_exists( ACC_PATH . 'includes/helpers/svc-rewards.php' ) ) {
            require_once ACC_PATH . 'includes/helpers/svc-rewards.php';
            Svc_Rewards::on_supplier_event( 'fast_shipping', [ 'ref_id' => $id, 'dedupe_key' => 'ship_' . $id ] );
        }

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

    public static function list_equipment_inquiries( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;
        $rows = $wpdb->get_results(
            "SELECT * FROM {$wpdb->prefix}svc_equipment_inquiries ORDER BY created_at DESC LIMIT 100",
            ARRAY_A
        ) ?: [];
        foreach ( $rows as &$r ) {
            $r['quote_options'] = json_decode( $r['quote_options'] ?? '[]', true ) ?: [];
        }
        return new WP_REST_Response( [ 'data' => $rows ] );
    }

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

        $update = [ 'updated_at' => current_time( 'mysql' ) ];
        foreach ( [ 'status', 'selected_option', 'notes', 'project_city', 'application' ] as $f ) {
            if ( null !== $req->get_param( $f ) ) {
                $update[ $f ] = sanitize_text_field( (string) $req->get_param( $f ) );
            }
        }
        if ( null !== $req->get_param( 'quote_options' ) ) {
            $opts = $req->get_param( 'quote_options' );
            $update['quote_options'] = is_string( $opts ) ? $opts : wp_json_encode( $opts, JSON_UNESCAPED_UNICODE );
        }

        $wpdb->update( "{$wpdb->prefix}svc_equipment_inquiries", $update, [ 'id' => $id ] );

        $new_status = $update['status'] ?? $row['status'];
        if ( in_array( $new_status, [ 'quoted', 'installed' ], true ) && file_exists( ACC_PATH . 'includes/helpers/svc-rewards.php' ) ) {
            require_once ACC_PATH . 'includes/helpers/svc-rewards.php';
            $phone = $row['phone'] ?? '';
            if ( $phone ) {
                Svc_Rewards::on_customer_event( $phone, 'equipment_purchase', [
                    'ref_id' => $id, 'dedupe_key' => 'equip_done_' . $id,
                    'order_value' => (float) ( $req->get_param( 'order_value' ) ?? 10000000 ),
                ] );
            }
            $tech_id = (int) get_current_user_id();
            if ( $tech_id && $new_status === 'installed' ) {
                $tacct = Svc_Rewards::get_or_create_account( 'technician', [ 'user_id' => $tech_id ] );
                Svc_Rewards::earn( (int) $tacct['id'], 'equipment_sold', [
                    'ref_id' => $id, 'dedupe_key' => 'equip_sell_' . $id,
                    'order_value' => (float) ( $req->get_param( 'order_value' ) ?? 10000000 ),
                ] );
            }
        }

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

    public static function list_technicians( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;
        self::_ensure_teams();
        $uid   = self::_current_uid();
        $scope = Svc_Teams::scoped_technician_ids( $uid );

        $sql = "SELECT u.ID AS id, u.display_name AS name, u.user_email AS email
                FROM {$wpdb->users} u";
        $params = [];

        if ( is_array( $scope ) ) {
            if ( ! $scope ) {
                return new WP_REST_Response( [ 'data' => [] ] );
            }
            $ph = implode( ',', array_fill( 0, count( $scope ), '%d' ) );
            $sql .= " WHERE u.ID IN ({$ph})";
            $params = $scope;
        }

        $sql .= " ORDER BY u.display_name LIMIT 200";

        $rows = $params
            ? ( $wpdb->get_results( $wpdb->prepare( $sql, ...$params ), ARRAY_A ) ?: [] )
            : ( $wpdb->get_results( $sql, ARRAY_A ) ?: [] );

        return new WP_REST_Response( [ 'data' => $rows ] );
    }

    public static function my_service_role( WP_REST_Request $req ): WP_REST_Response {
        self::_ensure_teams();
        require_once ACC_PATH . 'includes/helpers/svc-supplier.php';
        $uid = self::_current_uid();
        $role = 'admin';
        $is_accountant = false;
        if ( function_exists( 'acl_user_can' ) ) {
            $is_accountant = acl_user_can( $uid, 'service.accounting' )
                || acl_user_can( $uid, 'service.accounting.post' )
                || acl_user_can( $uid, 'acc.post' );
        }
        if ( $is_accountant && ! Svc_Teams::is_admin( $uid ) && ! Svc_Teams::is_senior( $uid ) && ! Svc_Teams::is_technician( $uid ) ) {
            $role = 'accountant';
        } elseif ( Svc_Supplier::is_supplier( $uid ) && ! Svc_Teams::is_admin( $uid ) && ! Svc_Teams::is_senior( $uid ) && ! Svc_Teams::is_technician( $uid ) ) {
            $role = 'supplier';
        } elseif ( Svc_Teams::is_technician( $uid ) ) {
            $role = 'technician';
        } elseif ( Svc_Teams::is_senior( $uid ) ) {
            $role = 'senior';
        }
        return new WP_REST_Response( [
            'data' => [
                'user_id'      => $uid,
                'role'         => $role,
                'is_senior'    => Svc_Teams::is_senior( $uid ),
                'is_technician'=> Svc_Teams::is_technician( $uid ),
                'is_admin'     => Svc_Teams::is_admin( $uid ),
                'is_accountant'=> $is_accountant,
                'is_supplier'  => Svc_Supplier::is_supplier( $uid ),
                'senior_id'    => Svc_Teams::senior_of_technician( $uid ),
                'team_ids'     => Svc_Teams::is_senior( $uid ) ? Svc_Teams::team_technician_ids( $uid ) : [],
            ],
        ] );
    }

    public static function senior_team_list( WP_REST_Request $req ): WP_REST_Response {
        self::_ensure_teams();
        $uid = self::_current_uid();
        if ( ! Svc_Teams::is_senior( $uid ) && ! Svc_Teams::is_admin( $uid ) ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'دسترسی ندارید' ], 403 );
        }
        $senior_id = (int) ( $req->get_param( 'senior_id' ) ?? $uid );
        if ( ! Svc_Teams::is_admin( $uid ) && $senior_id !== $uid ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'دسترسی ندارید' ], 403 );
        }
        return new WP_REST_Response( [ 'data' => Svc_Teams::list_team( $senior_id ) ] );
    }

    public static function senior_team_add( WP_REST_Request $req ): WP_REST_Response {
        self::_ensure_teams();
        $uid = self::_current_uid();
        if ( ! Svc_Teams::is_senior( $uid ) && ! Svc_Teams::is_admin( $uid ) ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'دسترسی ندارید' ], 403 );
        }
        $senior_id = Svc_Teams::is_admin( $uid ) && $req->get_param( 'senior_id' )
            ? (int) $req->get_param( 'senior_id' )
            : $uid;
        $tech_id = (int) ( $req->get_param( 'technician_user_id' ) ?? 0 );
        if ( ! $tech_id ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'کارشناس انتخاب نشده' ], 400 );
        }
        $ok = Svc_Teams::add_team_member( $senior_id, $tech_id );
        return new WP_REST_Response( [ 'success' => $ok, 'data' => Svc_Teams::list_team( $senior_id ) ] );
    }

    public static function senior_team_remove( WP_REST_Request $req ): WP_REST_Response {
        self::_ensure_teams();
        $uid = self::_current_uid();
        if ( ! Svc_Teams::is_senior( $uid ) && ! Svc_Teams::is_admin( $uid ) ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'دسترسی ندارید' ], 403 );
        }
        $senior_id = Svc_Teams::is_admin( $uid ) && $req->get_param( 'senior_id' )
            ? (int) $req->get_param( 'senior_id' )
            : $uid;
        $tech_id = (int) $req->get_param( 'technician_id' );
        Svc_Teams::remove_team_member( $senior_id, $tech_id );
        return new WP_REST_Response( [ 'success' => true, 'data' => Svc_Teams::list_team( $senior_id ) ] );
    }

    public static function senior_dashboard( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;
        self::_ensure_teams();
        $uid = self::_current_uid();
        if ( ! Svc_Teams::is_senior( $uid ) && ! Svc_Teams::is_admin( $uid ) ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'دسترسی ندارید' ], 403 );
        }
        $team = Svc_Teams::team_technician_ids( $uid );
        if ( ! $team ) {
            return new WP_REST_Response( [ 'data' => [ 'team_size' => 0, 'open' => 0, 'assigned' => 0 ] ] );
        }
        $ph = implode( ',', array_fill( 0, count( $team ), '%d' ) );
        $open = (int) $wpdb->get_var( $wpdb->prepare(
            "SELECT COUNT(*) FROM {$wpdb->prefix}svc_requests
             WHERE status IN ('submitted','assigned','in_progress')
             AND (assigned_technician_id IN ({$ph}) OR assigned_technician_id IS NULL OR assigned_technician_id = 0)",
            ...$team
        ) );
        $assigned = (int) $wpdb->get_var( $wpdb->prepare(
            "SELECT COUNT(*) FROM {$wpdb->prefix}svc_requests
             WHERE assigned_technician_id IN ({$ph}) AND status IN ('assigned','in_progress')",
            ...$team
        ) );
        return new WP_REST_Response( [
            'data' => [
                'team_size' => count( $team ),
                'open'      => $open,
                'assigned'  => $assigned,
                'team'      => Svc_Teams::list_team( $uid ),
            ],
        ] );
    }

    public static function technician_my_queue( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;
        self::_ensure_teams();
        $uid = self::_current_uid();
        if ( ! Svc_Teams::is_technician( $uid ) && ! Svc_Teams::is_admin( $uid ) ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'دسترسی ندارید' ], 403 );
        }
        $tech_id = Svc_Teams::is_admin( $uid ) && $req->get_param( 'technician_id' )
            ? (int) $req->get_param( 'technician_id' )
            : $uid;

        $rows = $wpdb->get_results( $wpdb->prepare(
            "SELECT r.*, u.display_name AS technician_name
             FROM {$wpdb->prefix}svc_requests r
             LEFT JOIN {$wpdb->users} u ON u.ID = r.assigned_technician_id
             WHERE r.assigned_technician_id = %d
               AND r.status IN ('assigned','in_progress','submitted')
             ORDER BY CASE WHEN r.priority='urgent' THEN 0 ELSE 1 END,
                      r.scheduled_at ASC, r.created_at DESC
             LIMIT 50",
            $tech_id
        ), ARRAY_A ) ?: [];

        foreach ( $rows as &$r ) {
            self::_enrich_request_row( $r );
            $r['draft_report_id'] = (int) $wpdb->get_var( $wpdb->prepare(
                "SELECT id FROM {$wpdb->prefix}svc_reports
                 WHERE request_id=%d AND status='draft' ORDER BY id DESC LIMIT 1",
                (int) $r['id']
            ) );
        }

        return new WP_REST_Response( [ 'data' => $rows ] );
    }

    public static function convert_visit_to_repair_quote( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;
        $id = (int) $req->get_param( 'id' );
        $row = $wpdb->get_row( $wpdb->prepare(
            "SELECT * FROM {$wpdb->prefix}svc_reports WHERE id=%d", $id
        ), ARRAY_A );
        if ( ! $row || $row['status'] === 'final' ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'گزارش قابل تبدیل نیست' ], 400 );
        }

        self::_decode_report_json( $row );
        $lines = is_array( $row['service_lines'] ?? null ) ? $row['service_lines'] : [];
        $lines[] = [
            'desc' => 'پیش‌فاکتور تعمیر (پس از بازدید)',
            'qty' => 1,
            'unit' => 'دستگاه',
            'unit_price' => (float) ( $req->get_param( 'estimate' ) ?? 0 ),
            'total' => (float) ( $req->get_param( 'estimate' ) ?? 0 ),
        ];

        $wpdb->update( "{$wpdb->prefix}svc_reports", [
            'service_type'   => 'repair',
            'service_lines'  => wp_json_encode( $lines, JSON_UNESCAPED_UNICODE ),
            'subtotal_services' => array_sum( array_map( fn( $l ) => (float) ( $l['total'] ?? 0 ), $lines ) ),
            'updated_at'     => current_time( 'mysql' ),
        ], [ 'id' => $id ] );

        $saved = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}svc_reports WHERE id=%d", $id ), ARRAY_A );
        self::_decode_report_json( $saved );
        self::_recalc_totals( $saved );
        $wpdb->update( "{$wpdb->prefix}svc_reports", [
            'total' => $saved['total'],
            'balance' => $saved['balance'],
            'subtotal_services' => $saved['subtotal_services'],
        ], [ 'id' => $id ] );

        if ( ! empty( $saved['request_id'] ) ) {
            $wpdb->update( "{$wpdb->prefix}svc_requests", [
                'service_type' => 'repair',
                'cost_estimate' => (float) $saved['total'],
                'updated_at' => current_time( 'mysql' ),
            ], [ 'id' => (int) $saved['request_id'] ] );
        }

        return new WP_REST_Response( [ 'success' => true, 'data' => $saved ] );
    }

    public static function senior_review_report( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;
        self::_ensure_teams();
        $uid = self::_current_uid();
        if ( ! Svc_Teams::is_senior( $uid ) && ! Svc_Teams::is_admin( $uid ) ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'دسترسی ندارید' ], 403 );
        }

        $id = (int) $req->get_param( 'id' );
        $status = sanitize_key( $req->get_param( 'status' ) ?? 'approved' );
        if ( ! in_array( $status, [ 'approved', 'rejected' ], true ) ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'وضعیت نامعتبر' ], 400 );
        }

        $wpdb->update( "{$wpdb->prefix}svc_reports", [
            'senior_review_status' => $status,
            'senior_review_note'   => sanitize_textarea_field( $req->get_param( 'note' ) ?? '' ),
            'senior_reviewed_by'   => $uid,
            'senior_reviewed_at'   => current_time( 'mysql' ),
            'updated_at'           => current_time( 'mysql' ),
        ], [ 'id' => $id ] );

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

    public static function list_reports_pending_review( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;
        self::_ensure_teams();
        $uid = self::_current_uid();
        if ( ! Svc_Teams::is_senior( $uid ) && ! Svc_Teams::is_admin( $uid ) ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'دسترسی ندارید' ], 403 );
        }

        $rows = $wpdb->get_results(
            "SELECT id, report_no, customer_name, site_label, total, created_by, finalized_at, senior_review_status
             FROM {$wpdb->prefix}svc_reports
             WHERE status='final' AND senior_review_status='pending'
             ORDER BY finalized_at DESC LIMIT 50",
            ARRAY_A
        ) ?: [];

        return new WP_REST_Response( [ 'data' => $rows ] );
    }

    public static function search_parts( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;
        $q = sanitize_text_field( $req->get_param( 'q' ) ?? '' );
        if ( strlen( $q ) < 2 ) {
            return new WP_REST_Response( [ 'data' => [] ] );
        }
        $like = '%' . $wpdb->esc_like( $q ) . '%';
        $rows = $wpdb->get_results( $wpdb->prepare(
            "SELECT p.id, p.code, p.name, p.unit,
                    COALESCE(SUM(s.qty),0) AS stock_qty
             FROM {$wpdb->prefix}inv_products p
             LEFT JOIN {$wpdb->prefix}inv_stock s ON s.product_id = p.id
             WHERE p.is_active = 1 AND (p.code LIKE %s OR p.name LIKE %s)
             GROUP BY p.id
             ORDER BY p.name LIMIT 20",
            $like, $like
        ), ARRAY_A ) ?: [];
        return new WP_REST_Response( [ 'data' => $rows ] );
    }

    public static function report_html( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;
        $id  = (int) $req->get_param( 'id' );
        $row = $wpdb->get_row( $wpdb->prepare(
            "SELECT * FROM {$wpdb->prefix}svc_reports WHERE id = %d", $id
        ), ARRAY_A );
        if ( ! $row ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'یافت نشد' ], 404 );
        }
        require_once ACC_PATH . 'includes/helpers/service-report-render.php';
        return new WP_REST_Response( [
            'data' => [
                'html'       => Service_Report_Render::render_html( $row, true ),
                'public_url' => Service_Report_Render::public_url( $row['public_token'] ?? '' ),
            ],
        ] );
    }

    public static function list_pm_contracts( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;
        $table = $wpdb->prefix . 'svc_pm_contracts';
        if ( $wpdb->get_var( "SHOW TABLES LIKE '{$table}'" ) !== $table ) {
            return new WP_REST_Response( [ 'data' => [] ] );
        }
        $rows = $wpdb->get_results(
            "SELECT * FROM {$table} ORDER BY next_visit_date ASC LIMIT 100",
            ARRAY_A
        ) ?: [];
        return new WP_REST_Response( [ 'data' => $rows ] );
    }

    public static function update_pm_contract( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;
        $id    = (int) $req->get_param( 'id' );
        $table = $wpdb->prefix . 'svc_pm_contracts';

        if ( $wpdb->get_var( "SHOW TABLES LIKE '{$table}'" ) !== $table ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'جدول PM وجود ندارد' ], 404 );
        }

        $update = [];
        if ( null !== $req->get_param( 'status' ) ) {
            $update['status'] = sanitize_key( $req->get_param( 'status' ) );
        }
        if ( null !== $req->get_param( 'plan' ) ) {
            $update['plan'] = sanitize_key( $req->get_param( 'plan' ) );
        }
        if ( null !== $req->get_param( 'next_visit_date' ) ) {
            $update['next_visit_date'] = sanitize_text_field( $req->get_param( 'next_visit_date' ) ) ?: null;
        }

        if ( $update ) {
            $wpdb->update( $table, $update, [ 'id' => $id ] );
        }

        $row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$table} WHERE id = %d", $id ), ARRAY_A );
        return new WP_REST_Response( [ 'success' => true, 'data' => $row ] );
    }

    public static function run_pm_reminders( WP_REST_Request $req ): WP_REST_Response {
        $result = self::_process_pm_reminders();
        return new WP_REST_Response( [ 'success' => true, 'data' => $result ] );
    }

    /** Cron entry — فراخوانی روزانه */
    public static function cron_pm_reminders(): array {
        return self::_process_pm_reminders();
    }

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

        $device = $wpdb->get_row( $wpdb->prepare(
            "SELECT * FROM {$wpdb->prefix}svc_devices WHERE id = %d", $id
        ), ARRAY_A );
        if ( ! $device ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'دستگاه یافت نشد' ], 404 );
        }

        $monthly = $wpdb->get_results( $wpdb->prepare(
            "SELECT DATE_FORMAT(COALESCE(finalized_at, created_at), '%%Y-%%m') AS month,
                    SUM(total) AS total, COUNT(*) AS cnt
             FROM {$wpdb->prefix}svc_reports
             WHERE device_id = %d AND status = 'final'
             GROUP BY month ORDER BY month ASC",
            $id
        ), ARRAY_A ) ?: [];

        $lifetime = (float) $wpdb->get_var( $wpdb->prepare(
            "SELECT COALESCE(SUM(total),0) FROM {$wpdb->prefix}svc_reports
             WHERE device_id = %d AND status = 'final'", $id
        ) );

        $estimated = (float) ( $device['estimated_value'] ?? 0 );
        $threshold = (float) get_option( 'acc_svc_replace_threshold_pct', 70 );
        $replace_suggested = $estimated > 0 && $lifetime >= ( $estimated * $threshold / 100 );

        return new WP_REST_Response( [
            'data' => [
                'device'            => $device,
                'monthly'           => $monthly,
                'lifetime_cost'     => $lifetime,
                'estimated_value'   => $estimated,
                'replace_suggested' => $replace_suggested,
                'replace_message'   => $replace_suggested
                    ? 'مجموع هزینه تعمیرات از ' . $threshold . '% ارزش دستگاه بیشتر شده — تعویض توصیه می‌شود.'
                    : '',
            ],
        ] );
    }

    public static function public_pay_request( WP_REST_Request $req ): WP_REST_Response {
        global $wpdb;

        $token = sanitize_text_field( $req->get_param( 'token' ) ?? '' );
        if ( ! $token ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'token الزامی است' ], 400 );
        }

        $report = $wpdb->get_row( $wpdb->prepare(
            "SELECT * FROM {$wpdb->prefix}svc_reports WHERE public_token = %s AND status = 'final' LIMIT 1",
            $token
        ), ARRAY_A );
        if ( ! $report ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'گزارش یافت نشد' ], 404 );
        }

        $balance = (float) $report['balance'];
        if ( $balance <= 0 ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => 'بدهی‌ای برای پرداخت وجود ندارد' ], 400 );
        }

        require_once ACC_PATH . 'includes/helpers/sr-zarinpal.php';
        $amount_rial = (int) round( $balance * 10 );
        $desc = 'پرداخت گزارش خدمات ' . $report['report_no'];
        $zp = SR_Zarinpal::request( $amount_rial, SR_Zarinpal::callback_url(), $desc, $report['phone'] ?? '', $report['email'] ?? '' );

        if ( ! $zp['success'] ) {
            return new WP_REST_Response( [ 'success' => false, 'message' => $zp['message'] ?? 'خطای درگاه' ], 502 );
        }

        $pay_table = $wpdb->prefix . 'svc_payments';
        if ( $wpdb->get_var( "SHOW TABLES LIKE '{$pay_table}'" ) === $pay_table ) {
            $wpdb->insert( $pay_table, [
                'report_id'    => (int) $report['id'],
                'amount'       => $balance,
                'authority'    => $zp['authority'],
                'status'       => 'pending',
                'created_at'   => current_time( 'mysql' ),
            ] );
        }

        return new WP_REST_Response( [
            'success'     => true,
            'data'        => [
                'payment_url' => $zp['payment_url'],
                'amount'      => $balance,
                'report_no'   => $report['report_no'],
            ],
        ] );
    }

    public static function public_pay_verify( WP_REST_Request $req ) {
        global $wpdb;

        $authority = sanitize_text_field( $req->get_param( 'Authority' ) ?? $req->get_param( 'authority' ) ?? '' );
        $status    = sanitize_text_field( $req->get_param( 'Status' ) ?? $req->get_param( 'status' ) ?? '' );

        $redirect_ok  = self::_pay_result_url( true );
        $redirect_fail = self::_pay_result_url( false );

        if ( strtoupper( $status ) !== 'OK' || ! $authority ) {
            self::_redirect_exit( $redirect_fail . '&reason=cancelled' );
        }

        $pay_table = $wpdb->prefix . 'svc_payments';
        $payment = $wpdb->get_row( $wpdb->prepare(
            "SELECT * FROM {$pay_table} WHERE authority = %s AND status = 'pending' ORDER BY id DESC LIMIT 1",
            $authority
        ), ARRAY_A );

        if ( ! $payment ) {
            self::_redirect_exit( $redirect_fail . '&reason=notfound' );
        }

        require_once ACC_PATH . 'includes/helpers/sr-zarinpal.php';
        $amount_rial = (int) round( (float) $payment['amount'] * 10 );
        $verify = SR_Zarinpal::verify( $authority, $amount_rial );

        if ( ! $verify['success'] ) {
            $wpdb->update( $pay_table, [ 'status' => 'failed' ], [ 'id' => (int) $payment['id'] ] );
            self::_redirect_exit( $redirect_fail . '&reason=verify' );
        }

        $report_id = (int) $payment['report_id'];
        $report = $wpdb->get_row( $wpdb->prepare(
            "SELECT * FROM {$wpdb->prefix}svc_reports WHERE id = %d", $report_id
        ), ARRAY_A );

        if ( $report ) {
            $paid    = (float) $report['paid'] + (float) $payment['amount'];
            $total   = (float) $report['total'];
            $balance = max( 0, $total - $paid );
            $wpdb->update( "{$wpdb->prefix}svc_reports", [
                'paid'    => $paid,
                'balance' => $balance,
                'updated_at' => current_time( 'mysql' ),
            ], [ 'id' => $report_id ] );
        }

        $wpdb->update( $pay_table, [
            'status'  => 'paid',
            'ref_id'  => $verify['ref_id'] ?? '',
            'paid_at' => current_time( 'mysql' ),
        ], [ 'id' => (int) $payment['id'] ] );

        if ( file_exists( ACC_PATH . 'includes/helpers/svc-rewards.php' ) ) {
            require_once ACC_PATH . 'includes/helpers/svc-rewards.php';
            Svc_Rewards::on_payment_success( $report_id, (float) $payment['amount'] );
        }

        if ( file_exists( ACC_PATH . 'includes/helpers/svc-accounting.php' ) ) {
            require_once ACC_PATH . 'includes/helpers/svc-accounting.php';
            Svc_Accounting::on_payment_received( $report_id, (float) $payment['amount'], (int) $payment['id'] );
        }

        self::_redirect_exit( $redirect_ok . '&ref=' . rawurlencode( $verify['ref_id'] ?? '' ) );
    }

    public static function meta( WP_REST_Request $req ): WP_REST_Response {
        return new WP_REST_Response( [
            'service_types'   => self::$SERVICE_TYPES,
            'request_status'  => self::$REQUEST_STATUS,
            'complaint_tags'  => [
                'کاهش برودت', 'خاموش شدن کمپرسور', 'مصرف برق بالا',
                'نشتی مبرد', 'صدای غیرعادی', 'سرویس دوره‌ای',
            ],
            'pm_options'      => [
                'monthly' => 'ماهانه',
                'quarterly' => 'سه‌ماهه',
                'semi_annual' => 'شش‌ماهه',
                'annual' => 'سالانه',
            ],
            'pm_prices'       => [
                'monthly'     => 1500000,
                'quarterly'   => 4000000,
                'semi_annual' => 7500000,
                'annual'      => 14000000,
            ],
            'extra_cost_presets' => [
                [ 'desc' => 'ایاب و ذهاب', 'amount' => 500000 ],
                [ 'desc' => 'حمل تجهیزات', 'amount' => 800000 ],
                [ 'desc' => 'هزینه جرثقیل', 'amount' => 2000000 ],
                [ 'desc' => 'کار در خارج از ساعت اداری', 'amount' => 1200000 ],
                [ 'desc' => 'کار در روز تعطیل', 'amount' => 1800000 ],
            ],
            'company_stamp_default' => get_option( 'acc_svc_company_stamp', 'مهر و امضای شرکت خدمات HVAC' ),
        ] );
    }

    /* ══════════════════════════════════════════════
       INTERNAL HELPERS
    ══════════════════════════════════════════════ */

    private static function _create_request_with_chat( array $data, bool $from_visitor = true ): array {
        global $wpdb;

        $tracking_code = self::_generate_tracking_code();
        $request_no    = self::_generate_request_no();
        $now           = current_time( 'mysql' );
        $session_key   = $data['session_key'] ?? '';

        // ثبت visitor
        $ip = $_SERVER['REMOTE_ADDR'] ?? '';
        $ua = substr( sanitize_text_field( $_SERVER['HTTP_USER_AGENT'] ?? '' ), 0, 500 );
        $wpdb->query( $wpdb->prepare(
            "INSERT INTO {$wpdb->prefix}chat_visitors (visitor_id, name, phone, email, ip_address, user_agent, created_at)
             VALUES (%s, %s, %s, %s, %s, %s, %s)
             ON DUPLICATE KEY UPDATE
                 name  = IF(VALUES(name) != '', VALUES(name), name),
                 phone = IF(VALUES(phone) != '', VALUES(phone), phone),
                 email = IF(VALUES(email) != '', VALUES(email), email)",
            $session_key,
            $data['customer_name'] ?? 'بازدیدکننده',
            $data['phone'] ?? '',
            $data['email'] ?? '',
            $ip,
            $ua,
            $now
        ) );

        // ایجاد گفتگو
        $wpdb->insert( "{$wpdb->prefix}chat_conversations", [
            'visitor_id'    => $session_key,
            'tracking_code' => $tracking_code,
            'agent_id'      => null,
            'status'        => 'open',
            'source'        => 'service',
            'created_at'    => $now,
            'updated_at'    => $now,
        ] );
        $conv_id = (int) $wpdb->insert_id;

        $type_label = self::$SERVICE_TYPES[ $data['service_type'] ?? 'repair' ] ?? '';
        $site       = $data['site_label'] ?? '';
        $welcome    = get_option( 'acc_chat_welcome_msg', 'سلام! به پشتیبانی خوش آمدید.' );
        $code_msg   = "🎫 کد پیگیری: {$tracking_code}\n📋 درخواست: {$type_label}" . ( $site ? "\n📍 موضع: {$site}" : '' );

        $wpdb->insert( "{$wpdb->prefix}chat_messages", [
            'conversation_id' => $conv_id,
            'sender_type'     => 'agent',
            'sender_id'       => '0',
            'message'         => $welcome,
            'created_at'      => $now,
        ] );
        $wpdb->insert( "{$wpdb->prefix}chat_messages", [
            'conversation_id' => $conv_id,
            'sender_type'     => 'system',
            'sender_id'       => '0',
            'message'         => $code_msg,
            'created_at'      => $now,
        ] );

        // دستگاه (اختیاری)
        $device_id = null;
        if ( ! empty( $data['device_id'] ) ) {
            $device_id = (int) $data['device_id'];
        } elseif ( ! empty( $data['serial_no'] ) || ! empty( $data['device_type'] ) ) {
            $wpdb->insert( "{$wpdb->prefix}svc_devices", [
                'site_label'       => $data['site_label'] ?? '',
                'customer_name'    => $data['customer_name'] ?? '',
                'phone'            => $data['phone'] ?? '',
                'device_type'      => $data['device_type'] ?? '',
                'brand'            => $data['brand'] ?? '',
                'model'            => $data['model'] ?? '',
                'serial_no'        => $data['serial_no'] ?? '',
                'capacity'         => $data['capacity'] ?? '',
                'refrigerant_type' => $data['refrigerant'] ?? '',
                'install_location' => $data['install_location'] ?? '',
                'operating_hours'  => (int) ( $data['operating_hours'] ?? 0 ),
                'created_at'       => $now,
                'updated_at'       => $now,
            ] );
            $device_id = (int) $wpdb->insert_id;
        }

        $wpdb->insert( "{$wpdb->prefix}svc_requests", [
            'request_no'             => $request_no,
            'tracking_code'          => $tracking_code,
            'conversation_id'        => $conv_id,
            'session_key'            => $session_key,
            'service_type'           => $data['service_type'] ?? 'repair',
            'status'                 => 'submitted',
            'priority'               => $data['priority'] ?? 'normal',
            'cost_estimate'          => (float) ( $data['cost_estimate'] ?? 0 ),
            'extra_data'             => $data['extra_data'] ?? null,
            'customer_name'          => $data['customer_name'] ?? '',
            'company'                => $data['company'] ?? '',
            'phone'                  => $data['phone'] ?? '',
            'email'                  => $data['email'] ?? '',
            'address'                => $data['address'] ?? '',
            'site_label'             => $data['site_label'] ?? '',
            'device_id'              => $device_id,
            'device_type'            => $data['device_type'] ?? '',
            'brand'                  => $data['brand'] ?? '',
            'model'                  => $data['model'] ?? '',
            'serial_no'              => $data['serial_no'] ?? '',
            'capacity'               => $data['capacity'] ?? '',
            'refrigerant'            => $data['refrigerant'] ?? '',
            'install_location'       => $data['install_location'] ?? '',
            'complaint_text'         => $data['complaint_text'] ?? '',
            'complaint_tags'         => $data['complaint_tags'] ?? '[]',
            'created_at'             => $now,
            'updated_at'             => $now,
        ] );
        $req_id = (int) $wpdb->insert_id;

        return [
            'success' => true,
            'data'    => [
                'id'                  => $req_id,
                'request_no'          => $request_no,
                'tracking_code'       => $tracking_code,
                'conversation_id'     => $conv_id,
                'room_id'             => $conv_id,
                'service_type'        => $data['service_type'] ?? 'repair',
                'service_type_label'  => $type_label,
                'site_label'          => $data['site_label'] ?? '',
                'status'              => 'submitted',
            ],
        ];
    }

    private static function _get_request( int $id ): ?array {
        global $wpdb;
        $row = $wpdb->get_row( $wpdb->prepare(
            "SELECT r.*, u.display_name AS technician_name
             FROM {$wpdb->prefix}svc_requests r
             LEFT JOIN {$wpdb->users} u ON u.ID = r.assigned_technician_id
             WHERE r.id = %d", $id
        ), ARRAY_A );
        if ( ! $row ) return null;
        $row['service_type_label'] = self::$SERVICE_TYPES[ $row['service_type'] ] ?? $row['service_type'];
        $row['status_label']       = self::$REQUEST_STATUS[ $row['status'] ] ?? $row['status'];
        return $row;
    }

    private static function _maybe_create_pm_contract( array $report ): void {
        global $wpdb;
        $pm = $report['pm_offer'] ?? [];
        if ( empty( $pm['selected'] ) || empty( $pm['plan'] ) ) return;

        $table = $wpdb->prefix . 'svc_pm_contracts';
        if ( $wpdb->get_var( "SHOW TABLES LIKE '{$table}'" ) !== $table ) return;

        $intervals = [
            'monthly'     => '+1 month',
            'quarterly'   => '+3 months',
            'semi_annual' => '+6 months',
            'annual'      => '+1 year',
        ];
        $plan = $pm['plan'];
        $next = isset( $intervals[ $plan ] )
            ? date( 'Y-m-d', strtotime( $intervals[ $plan ] ) )
            : date( 'Y-m-d', strtotime( '+1 month' ) );

        $wpdb->insert( $table, [
            'report_id'       => (int) ( $report['id'] ?? 0 ),
            'device_id'       => $report['device_id'] ?? null,
            'customer_name'   => $report['customer_name'] ?? '',
            'phone'           => $report['phone'] ?? '',
            'site_label'      => $report['site_label'] ?? '',
            'plan'            => $plan,
            'status'          => 'active',
            'next_visit_date' => $next,
            'created_at'      => current_time( 'mysql' ),
        ] );
        $pm_id = (int) $wpdb->insert_id;
        if ( $plan === 'annual' && file_exists( ACC_PATH . 'includes/helpers/svc-rewards.php' ) ) {
            require_once ACC_PATH . 'includes/helpers/svc-rewards.php';
            Svc_Rewards::on_pm_contract( [ 'id' => $pm_id, 'phone' => $report['phone'] ?? '' ] );
        }
    }

    private static function _maybe_create_pm_preinvoice( array $report ): void {
        global $wpdb;
        $pm = is_array( $report['pm_offer'] ?? null ) ? $report['pm_offer'] : json_decode( $report['pm_offer'] ?? '{}', true );
        if ( empty( $pm['selected'] ) || empty( $pm['plan'] ) ) return;

        $prices = [
            'monthly' => 1500000, 'quarterly' => 4000000,
            'semi_annual' => 7500000, 'annual' => 14000000,
        ];
        $price = (float) ( $pm['estimated_price'] ?? $prices[ $pm['plan'] ] ?? 0 );
        if ( $price <= 0 ) return;

        $exists = (int) $wpdb->get_var( $wpdb->prepare(
            "SELECT COUNT(*) FROM {$wpdb->prefix}svc_reports
             WHERE request_id=%d AND service_type='pm' AND status='draft'",
            (int) ( $report['request_id'] ?? 0 )
        ) );
        if ( $exists ) return;

        $wpdb->insert( "{$wpdb->prefix}svc_reports", [
            'report_no'         => self::_generate_report_no(),
            'request_id'        => $report['request_id'] ?? null,
            'device_id'         => $report['device_id'] ?? null,
            'service_type'      => 'pm',
            'status'            => 'draft',
            'report_date'       => current_time( 'Y-m-d' ),
            'customer_name'     => $report['customer_name'] ?? '',
            'phone'             => $report['phone'] ?? '',
            'site_label'        => $report['site_label'] ?? '',
            'case_no'           => ( $report['case_no'] ?? '' ) . '-PM',
            'service_lines'     => wp_json_encode( [[
                'desc' => 'پیش‌فاکتور قرارداد PM — ' . ( $pm['plan'] ?? '' ),
                'qty' => 1, 'unit' => 'قرارداد', 'unit_price' => $price, 'total' => $price,
            ]], JSON_UNESCAPED_UNICODE ),
            'subtotal_services' => $price,
            'total'             => $price,
            'balance'           => $price,
            'public_token'      => self::_generate_public_token(),
            'created_by'        => get_current_user_id() ?: 0,
            'created_at'        => current_time( 'mysql' ),
            'updated_at'        => current_time( 'mysql' ),
        ] );
    }

    private static function _link_device_from_report( array $report ): void {
        global $wpdb;
        if ( ! empty( $report['device_id'] ) ) return;

        $snap = is_array( $report['device_snapshot'] ?? null )
            ? $report['device_snapshot']
            : json_decode( $report['device_snapshot'] ?? '{}', true );
        $phone = $report['phone'] ?? '';
        if ( ! $phone && empty( $snap['serial_no'] ) ) return;

        $device_id = null;
        if ( ! empty( $snap['serial_no'] ) ) {
            $device_id = (int) $wpdb->get_var( $wpdb->prepare(
                "SELECT id FROM {$wpdb->prefix}svc_devices WHERE serial_no=%s LIMIT 1",
                $snap['serial_no']
            ) );
        }
        if ( ! $device_id && $phone ) {
            $device_id = (int) $wpdb->get_var( $wpdb->prepare(
                "SELECT id FROM {$wpdb->prefix}svc_devices WHERE phone=%s ORDER BY updated_at DESC LIMIT 1",
                $phone
            ) );
        }
        if ( ! $device_id ) {
            $wpdb->insert( "{$wpdb->prefix}svc_devices", [
                'customer_name'     => $report['customer_name'] ?? '',
                'phone'             => $phone,
                'site_label'        => $report['site_label'] ?? '',
                'device_type'       => $snap['device_type'] ?? '',
                'brand'             => $snap['brand'] ?? '',
                'model'             => $snap['model'] ?? '',
                'serial_no'         => $snap['serial_no'] ?? '',
                'capacity'          => $snap['capacity'] ?? '',
                'refrigerant'       => $snap['refrigerant'] ?? '',
                'install_location'  => $snap['install_location'] ?? '',
                'operating_hours'   => (int) ( $snap['operating_hours'] ?? 0 ),
                'last_service_date' => $snap['last_service_date'] ?? current_time( 'Y-m-d' ),
                'created_at'        => current_time( 'mysql' ),
                'updated_at'        => current_time( 'mysql' ),
            ] );
            $device_id = (int) $wpdb->insert_id;
        } else {
            $wpdb->update( "{$wpdb->prefix}svc_devices", [
                'operating_hours'   => (int) ( $snap['operating_hours'] ?? 0 ),
                'last_service_date' => current_time( 'Y-m-d' ),
                'updated_at'        => current_time( 'mysql' ),
            ], [ 'id' => $device_id ] );
        }

        $wpdb->update( "{$wpdb->prefix}svc_reports", [
            'device_id' => $device_id,
        ], [ 'id' => (int) ( $report['id'] ?? 0 ) ] );

        if ( ! empty( $report['request_id'] ) ) {
            $wpdb->update( "{$wpdb->prefix}svc_requests", [
                'device_id' => $device_id,
            ], [ 'id' => (int) $report['request_id'] ] );
        }
    }

    private static function _get_request_by_code( string $code ): ?array {
        global $wpdb;
        return $wpdb->get_row( $wpdb->prepare(
            "SELECT * FROM {$wpdb->prefix}svc_requests WHERE tracking_code = %s LIMIT 1", $code
        ), ARRAY_A ) ?: null;
    }

    private static function _request_timeline( int $request_id ): array {
        global $wpdb;
        $items = [];

        $req = self::_get_request( $request_id );
        if ( $req ) {
            $items[] = [
                'type'  => 'created',
                'label' => 'ثبت درخواست — ' . ( $req['service_type_label'] ?? $req['service_type'] ),
                'at'    => $req['created_at'],
            ];

            $rank = [ 'submitted' => 0, 'assigned' => 1, 'in_progress' => 2, 'completed' => 3, 'cancelled' => -1 ];
            $cur  = $rank[ $req['status'] ] ?? 0;

            if ( $cur >= 1 && ! empty( $req['assigned_technician_id'] ) ) {
                $label = 'تخصیص کارشناس';
                if ( ! empty( $req['technician_name'] ) ) {
                    $label .= ' — ' . $req['technician_name'];
                }
                if ( ! empty( $req['scheduled_at'] ) ) {
                    $label .= ' · ' . substr( $req['scheduled_at'], 0, 16 );
                }
                $items[] = [ 'type' => 'assigned', 'label' => $label, 'at' => $req['scheduled_at'] ?: $req['updated_at'] ];
                if ( ! empty( $req['dispatch_notes'] ) ) {
                    $items[] = [ 'type' => 'dispatch_note', 'label' => 'دستور ارشد: ' . $req['dispatch_notes'], 'at' => $req['updated_at'] ];
                }
            }
            if ( $cur >= 2 ) {
                $items[] = [ 'type' => 'progress', 'label' => 'شروع کار میدانی', 'at' => $req['updated_at'] ];
            }
            if ( $cur >= 3 ) {
                $items[] = [ 'type' => 'completed', 'label' => 'تکمیل پرونده', 'at' => $req['updated_at'] ];
            }
        }

        $reports = $wpdb->get_results( $wpdb->prepare(
            "SELECT report_no, status, total, balance, paid, public_token, senior_review_status,
                    created_at, finalized_at, senior_reviewed_at
             FROM {$wpdb->prefix}svc_reports
             WHERE request_id = %d ORDER BY created_at ASC",
            $request_id
        ), ARRAY_A ) ?: [];

        foreach ( $reports as $r ) {
            $items[] = [
                'type'  => 'report',
                'label' => 'گزارش فنی ' . $r['report_no'] . ( $r['status'] === 'final' ? ' (نهایی)' : ' (پیش‌نویس)' ),
                'at'    => $r['finalized_at'] ?: $r['created_at'],
                'total' => $r['total'],
                'balance' => $r['balance'],
            ];
            if ( $r['status'] === 'final' && (float) ( $r['total'] ?? 0 ) > 0 ) {
                $items[] = [
                    'type'  => 'invoice',
                    'label' => 'صدور فاکتور — ' . number_format( (float) $r['total'] ) . ' تومان'
                        . ( (float) ( $r['balance'] ?? 0 ) > 0 ? ' · مانده: ' . number_format( (float) $r['balance'] ) : ' · تسویه' ),
                    'at'    => $r['finalized_at'] ?: $r['created_at'],
                    'total' => $r['total'],
                    'balance' => $r['balance'],
                    'public_token' => $r['public_token'],
                ];
            }
            if ( ( $r['senior_review_status'] ?? '' ) === 'approved' ) {
                $items[] = [
                    'type'  => 'senior_review',
                    'label' => 'تأیید گزارش توسط کارشناس ارشد',
                    'at'    => $r['senior_reviewed_at'] ?: $r['finalized_at'],
                ];
            } elseif ( ( $r['senior_review_status'] ?? '' ) === 'pending' ) {
                $items[] = [
                    'type'  => 'senior_review',
                    'label' => 'در انتظار بازبینی ارشد',
                    'at'    => $r['finalized_at'] ?: $r['created_at'],
                ];
            }
        }

        $parts = $wpdb->get_row( $wpdb->prepare(
            "SELECT id, status, part_number, quote_price, eta_days, updated_at, created_at
             FROM {$wpdb->prefix}svc_parts_orders WHERE request_id = %d LIMIT 1",
            $request_id
        ), ARRAY_A );
        if ( $parts ) {
            $status_lbl = [ 'pending' => 'در انتظار', 'quoted' => 'استعلام قیمت', 'ordered' => 'سفارش', 'shipped' => 'ارسال', 'delivered' => 'تحویل' ];
            $items[] = [
                'type'  => 'parts',
                'label' => 'قطعه ' . ( $parts['part_number'] ?: '—' ) . ' — ' . ( $status_lbl[ $parts['status'] ] ?? $parts['status'] ),
                'at'    => $parts['updated_at'] ?: $parts['created_at'],
                'total' => $parts['quote_price'],
            ];
        }

        $equip = $wpdb->get_row( $wpdb->prepare(
            "SELECT id, status, quote_options, updated_at, created_at
             FROM {$wpdb->prefix}svc_equipment_inquiries WHERE request_id = %d LIMIT 1",
            $request_id
        ), ARRAY_A );
        if ( $equip ) {
            $quotes = json_decode( $equip['quote_options'] ?? '[]', true ) ?: [];
            $items[] = [
                'type'  => 'equipment',
                'label' => 'استعلام تجهیزات — ' . ( $equip['status'] === 'quoted' ? 'پیشنهاد ' . count( $quotes ) . ' گزینه' : $equip['status'] ),
                'at'    => $equip['updated_at'] ?: $equip['created_at'],
            ];
        }

        usort( $items, fn( $a, $b ) => strcmp( $a['at'] ?? '', $b['at'] ?? '' ) );
        return $items;
    }

    private static function _parse_request_body( WP_REST_Request $req ): array {
        return [
            'customer_name'  => sanitize_text_field( $req->get_param( 'customer_name' ) ?? '' ),
            'company'        => sanitize_text_field( $req->get_param( 'company' ) ?? '' ),
            'phone'          => sanitize_text_field( $req->get_param( 'phone' ) ?? '' ),
            'email'          => sanitize_email( $req->get_param( 'email' ) ?? '' ),
            'address'        => sanitize_textarea_field( $req->get_param( 'address' ) ?? '' ),
            'site_label'     => sanitize_text_field( $req->get_param( 'site_label' ) ?? '' ),
            'service_type'   => sanitize_key( $req->get_param( 'service_type' ) ?? 'repair' ),
            'complaint_text' => sanitize_textarea_field( $req->get_param( 'complaint_text' ) ?? '' ),
            'device_type'    => sanitize_text_field( $req->get_param( 'device_type' ) ?? '' ),
            'brand'          => sanitize_text_field( $req->get_param( 'brand' ) ?? '' ),
            'model'          => sanitize_text_field( $req->get_param( 'model' ) ?? '' ),
            'serial_no'      => sanitize_text_field( $req->get_param( 'serial_no' ) ?? '' ),
            'capacity'       => sanitize_text_field( $req->get_param( 'capacity' ) ?? '' ),
            'refrigerant'    => sanitize_text_field( $req->get_param( 'refrigerant' ) ?? '' ),
            'install_location' => sanitize_text_field( $req->get_param( 'install_location' ) ?? '' ),
        ];
    }

    private static function _parse_device_body( WP_REST_Request $req ): array {
        return [
            'site_label'       => sanitize_text_field( $req->get_param( 'site_label' ) ?? '' ),
            'customer_name'    => sanitize_text_field( $req->get_param( 'customer_name' ) ?? '' ),
            'phone'            => sanitize_text_field( $req->get_param( 'phone' ) ?? '' ),
            'device_type'      => sanitize_text_field( $req->get_param( 'device_type' ) ?? '' ),
            'brand'            => sanitize_text_field( $req->get_param( 'brand' ) ?? '' ),
            'model'            => sanitize_text_field( $req->get_param( 'model' ) ?? '' ),
            'serial_no'        => sanitize_text_field( $req->get_param( 'serial_no' ) ?? '' ),
            'capacity'         => sanitize_text_field( $req->get_param( 'capacity' ) ?? '' ),
            'refrigerant_type' => sanitize_text_field( $req->get_param( 'refrigerant_type' ) ?? '' ),
            'install_location' => sanitize_text_field( $req->get_param( 'install_location' ) ?? '' ),
            'operating_hours'  => (int) ( $req->get_param( 'operating_hours' ) ?? 0 ),
            'last_service_date'=> sanitize_text_field( $req->get_param( 'last_service_date' ) ?? '' ),
            'notes'            => sanitize_textarea_field( $req->get_param( 'notes' ) ?? '' ),
            'estimated_value'  => (float) ( $req->get_param( 'estimated_value' ) ?? 0 ),
        ];
    }

    private static function _device_snapshot_from_request( ?array $request ): array {
        if ( ! $request ) return [];
        return [
            'device_type'       => $request['device_type'] ?? '',
            'brand'             => $request['brand'] ?? '',
            'model'             => $request['model'] ?? '',
            'serial_no'         => $request['serial_no'] ?? '',
            'capacity'          => $request['capacity'] ?? '',
            'refrigerant'       => $request['refrigerant'] ?? '',
            'install_location'  => $request['install_location'] ?? '',
            'operating_hours'   => (int) ( $request['operating_hours'] ?? 0 ),
            'last_service_date' => $request['last_service_date'] ?? '',
        ];
    }

    private static function _decode_report_json( array &$row ): void {
        foreach ( [
            'technical_readings', 'service_lines', 'part_lines', 'material_lines',
            'extra_costs', 'recommendations', 'pm_offer', 'complaint_tags', 'device_snapshot',
            'photos_before', 'photos_after',
        ] as $f ) {
            if ( isset( $row[ $f ] ) && is_string( $row[ $f ] ) ) {
                $row[ $f ] = json_decode( $row[ $f ], true ) ?: [];
            }
        }
        $row['service_type_label'] = self::$SERVICE_TYPES[ $row['service_type'] ?? '' ] ?? ( $row['service_type'] ?? '' );
    }

    private static function _enrich_request_row( array &$r ): void {
        $r['service_type_label'] = self::$SERVICE_TYPES[ $r['service_type'] ] ?? $r['service_type'];
        $r['status_label']       = self::$REQUEST_STATUS[ $r['status'] ] ?? $r['status'];
        $r['priority_label']     = ( $r['priority'] ?? '' ) === 'urgent' ? 'فوری' : 'عادی';
        if ( file_exists( ACC_PATH . 'includes/helpers/svc-rewards.php' ) && ! empty( $r['phone'] ) ) {
            require_once ACC_PATH . 'includes/helpers/svc-rewards.php';
            $r['has_priority_dispatch'] = Svc_Rewards::has_benefit( $r['phone'], 'priority_dispatch' );
        }
    }

    private static function _recalc_totals( array &$data ): void {
        $services  = (float) ( $data['subtotal_services'] ?? 0 );
        $parts     = (float) ( $data['subtotal_parts'] ?? 0 );
        $materials = (float) ( $data['subtotal_materials'] ?? 0 );
        $extra     = (float) ( $data['subtotal_extra'] ?? 0 );
        $discount  = (float) ( $data['discount'] ?? 0 );
        $tax       = (float) ( $data['tax'] ?? 0 );
        $paid      = (float) ( $data['paid'] ?? 0 );

        if ( isset( $data['service_lines'] ) && is_string( $data['service_lines'] ) ) {
            $lines = json_decode( $data['service_lines'], true ) ?: [];
            $services = array_sum( array_map( fn( $l ) => (float) ( $l['total'] ?? 0 ), $lines ) );
            $data['subtotal_services'] = $services;
        }
        if ( isset( $data['part_lines'] ) && is_string( $data['part_lines'] ) ) {
            $lines = json_decode( $data['part_lines'], true ) ?: [];
            $parts = array_sum( array_map( fn( $l ) => (float) ( $l['total'] ?? 0 ), $lines ) );
            $data['subtotal_parts'] = $parts;
        }
        if ( isset( $data['material_lines'] ) && is_string( $data['material_lines'] ) ) {
            $lines = json_decode( $data['material_lines'], true ) ?: [];
            $materials = array_sum( array_map( fn( $l ) => (float) ( $l['total'] ?? 0 ), $lines ) );
            $data['subtotal_materials'] = $materials;
        }
        if ( isset( $data['extra_costs'] ) && is_string( $data['extra_costs'] ) ) {
            $lines = json_decode( $data['extra_costs'], true ) ?: [];
            $extra = array_sum( array_map( fn( $l ) => (float) ( $l['amount'] ?? 0 ), $lines ) );
            $data['subtotal_extra'] = $extra;
        }

        $total = max( 0, $services + $parts + $materials + $extra - $discount + $tax );
        $data['total']   = $total;
        $data['balance'] = max( 0, $total - $paid );
    }

    private static function _generate_tracking_code(): string {
        global $wpdb;
        $chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
        for ( $i = 0; $i < 30; $i++ ) {
            $code = '';
            for ( $j = 0; $j < 6; $j++ ) {
                $code .= $chars[ random_int( 0, strlen( $chars ) - 1 ) ];
            }
            $exists = $wpdb->get_var( $wpdb->prepare(
                "SELECT id FROM {$wpdb->prefix}svc_requests WHERE tracking_code = %s
                 UNION SELECT id FROM {$wpdb->prefix}chat_conversations WHERE tracking_code = %s LIMIT 1",
                $code, $code
            ) );
            if ( ! $exists ) return $code;
        }
        return strtoupper( substr( md5( uniqid( '', true ) ), 0, 6 ) );
    }

    private static function _generate_request_no(): string {
        global $wpdb;
        $year = date( 'Y' );
        $cnt  = (int) $wpdb->get_var(
            "SELECT COUNT(*) FROM {$wpdb->prefix}svc_requests WHERE request_no LIKE 'SR-{$year}-%'"
        );
        return sprintf( 'SR-%s-%05d', $year, $cnt + 1 );
    }

    private static function _generate_report_no(): string {
        global $wpdb;
        $year = date( 'Y' );
        $cnt  = (int) $wpdb->get_var(
            "SELECT COUNT(*) FROM {$wpdb->prefix}svc_reports WHERE report_no LIKE 'SRD-{$year}-%'"
        );
        return sprintf( 'SRD-%s-%05d', $year, $cnt + 1 );
    }

    private static function _generate_public_token(): string {
        return bin2hex( random_bytes( 16 ) );
    }

    private static function _process_pm_reminders(): array {
        global $wpdb;
        $table = $wpdb->prefix . 'svc_pm_contracts';
        if ( $wpdb->get_var( "SHOW TABLES LIKE '{$table}'" ) !== $table ) {
            return [ 'created' => 0, 'checked' => 0 ];
        }

        $rows = $wpdb->get_results(
            "SELECT * FROM {$table} WHERE status = 'active' AND next_visit_date <= CURDATE() LIMIT 50",
            ARRAY_A
        ) ?: [];

        $created = 0;
        $today   = current_time( 'Y-m-d' );

        foreach ( $rows as $c ) {
            if ( ! empty( $c['last_reminder_at'] ) && substr( $c['last_reminder_at'], 0, 10 ) === $today ) {
                continue;
            }

            $data = [
                'session_key'    => 'pm_auto_' . $c['id'] . '_' . time(),
                'customer_name'  => $c['customer_name'] ?? '',
                'phone'          => $c['phone'] ?? '',
                'site_label'     => $c['site_label'] ?? '',
                'service_type'   => 'pm',
                'device_id'      => ! empty( $c['device_id'] ) ? (int) $c['device_id'] : null,
                'complaint_text' => 'یادآوری سرویس دوره‌ای — قرارداد ' . ( $c['plan'] ?? '' ),
            ];

            $result = self::_create_request_with_chat( $data, false );
            if ( ! $result['success'] ) continue;

            $wpdb->update( $table, [
                'last_request_id'  => (int) $result['data']['id'],
                'last_reminder_at' => current_time( 'mysql' ),
                'next_visit_date'  => self::_next_pm_date( $c['plan'] ?? 'monthly' ),
            ], [ 'id' => (int) $c['id'] ] );

            $created++;
        }

        return [ 'created' => $created, 'checked' => count( $rows ) ];
    }

    private static function _next_pm_date( string $plan ): string {
        $map = [
            'monthly'     => '+1 month',
            'quarterly'   => '+3 months',
            'semi_annual' => '+6 months',
            'annual'      => '+1 year',
        ];
        return date( 'Y-m-d', strtotime( $map[ $plan ] ?? '+1 month' ) );
    }

    private static function _pay_page_url( string $token ): string {
        $base = defined( 'ACC_URL' ) ? rtrim( ACC_URL, '/' ) : '';
        return $base . '/service-pay.php?token=' . rawurlencode( $token );
    }

    private static function _pay_result_url( bool $ok ): string {
        $base = defined( 'ACC_URL' ) ? rtrim( ACC_URL, '/' ) : '';
        return $base . '/service-pay.php?result=' . ( $ok ? 'success' : 'fail' );
    }

    private static function _redirect_exit( string $url ): void {
        if ( function_exists( 'wp_redirect' ) ) {
            wp_redirect( $url );
        } else {
            header( 'Location: ' . $url );
        }
        exit;
    }

    private static function _create_parts_order( int $req_id, string $code, array $data, array $quote ): void {
        global $wpdb;
        $extra = json_decode( $data['extra_data'] ?? '{}', true ) ?: [];
        $supplier_id = (int) ( $quote['supplier_id'] ?? 0 );
        $status = ! empty( $quote['in_stock'] ) ? 'ready'
            : ( ! empty( $quote['status'] ) && $quote['status'] === 'supplier_catalog' ? 'quoted' : 'supplier_pending' );
        $wpdb->insert( "{$wpdb->prefix}svc_parts_orders", [
            'request_id'    => $req_id,
            'tracking_code' => $code,
            'customer_name' => $data['customer_name'] ?? '',
            'phone'         => $data['phone'] ?? '',
            'part_number'   => $extra['part_number'] ?? '',
            'part_qty'      => (int) ( $extra['part_qty'] ?? 1 ),
            'status'        => $status,
            'supplier_id'   => $supplier_id ?: null,
            'quote_price'   => (float) ( $quote['price'] ?? 0 ),
            'deposit_amount'=> ! empty( $quote['in_stock'] ) ? 0 : (float) ( $quote['price'] ?? 0 ) * 0.3,
            'eta_days'      => (int) ( $quote['eta_days'] ?? 5 ),
            'in_stock'      => ! empty( $quote['in_stock'] ) ? 1 : 0,
            'notes'         => $supplier_id ? '[کاتالوگ تأمین‌کننده]' : '',
            'created_at'    => current_time( 'mysql' ),
        ] );
        $order_id = (int) $wpdb->insert_id;
        if ( file_exists( ACC_PATH . 'includes/helpers/svc-rewards.php' ) ) {
            require_once ACC_PATH . 'includes/helpers/svc-rewards.php';
            $phone = $data['phone'] ?? '';
            if ( $phone && Svc_Rewards::has_benefit( $phone, 'free_shipping' ) ) {
                $wpdb->update( "{$wpdb->prefix}svc_parts_orders", [
                    'notes' => '[ارسال رایگان — جایزه پاداش]',
                ], [ 'id' => $order_id ] );
            }
            if ( empty( $quote['in_stock'] ) ) {
                Svc_Rewards::on_supplier_event( 'fast_quote', [ 'ref_id' => $req_id, 'dedupe_key' => 'quote_' . $req_id ] );
            }
        }
    }

    private static function _create_equipment_inquiry( int $req_id, string $code, array $data, array $payload ): void {
        global $wpdb;
        $options = [
            [ 'key' => 'economy', 'label' => 'گزینه اقتصادی', 'price' => 0 ],
            [ 'key' => 'standard', 'label' => 'گزینه استاندارد', 'price' => 0 ],
            [ 'key' => 'premium', 'label' => 'گزینه پریمیوم', 'price' => 0 ],
        ];
        $wpdb->insert( "{$wpdb->prefix}svc_equipment_inquiries", [
            'request_id'        => $req_id,
            'tracking_code'     => $code,
            'customer_name'     => $data['customer_name'] ?? '',
            'phone'             => $data['phone'] ?? '',
            'project_city'      => $payload['project_city'] ?? '',
            'application'       => $payload['application'] ?? '',
            'required_capacity' => $payload['required_capacity'] ?? sanitize_text_field( $payload['capacity'] ?? '' ),
            'budget_range'      => $payload['budget_range'] ?? '',
            'status'            => 'inquiry',
            'quote_options'     => wp_json_encode( $options, JSON_UNESCAPED_UNICODE ),
            'created_at'        => current_time( 'mysql' ),
        ] );
    }
}
