<?php
/**
 * acc-event-engine.php — هسته یکپارچه رویداد → template → flow → ledger
 */
if ( ! defined( 'ABSPATH' ) ) exit;

class Acc_Event_Engine {

    private static array $_col_cache = [];

    public static function has_column( string $table_suffix, string $column ): bool {
        global $wpdb;
        $key = $table_suffix . '.' . $column;
        if ( isset( self::$_col_cache[ $key ] ) ) {
            return self::$_col_cache[ $key ];
        }
        $t = $wpdb->prefix . $table_suffix;
        self::$_col_cache[ $key ] = (bool) $wpdb->get_var( $wpdb->prepare(
            'SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s AND COLUMN_NAME = %s',
            $t, $column
        ) );
        return self::$_col_cache[ $key ];
    }

    /**
     * ایجاد رویداد با idempotency و مرجع یکپارچه
     */
    public static function create_event( array $args ): array {
        global $wpdb;
        $pre = $wpdb->prefix;

        if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $pre . 'ai_acc_events' ) ) !== $pre . 'ai_acc_events' ) {
            return [ 'success' => false, 'message' => 'موتور حسابداری نصب نشده', 'http' => 500 ];
        }

        $event_type = sanitize_text_field( $args['event_type'] ?? '' );
        if ( ! $event_type ) {
            return [ 'success' => false, 'message' => 'event_type الزامی است', 'http' => 400 ];
        }

        $amount  = (float) ( $args['amount'] ?? 0 );
        $payload = $args['payload'] ?? $args['metadata'] ?? [];
        if ( is_string( $payload ) ) {
            $payload = json_decode( $payload, true ) ?: [];
        }
        if ( ! is_array( $payload ) ) {
            $payload = [];
        }
        if ( ! isset( $payload['amount'] ) ) {
            $payload['amount'] = $amount;
        }

        $idempotency = $args['idempotency_key'] ?? '';
        if ( $idempotency && self::has_column( 'ai_acc_events', 'idempotency_key' ) ) {
            $existing = (int) $wpdb->get_var( $wpdb->prepare(
                "SELECT id FROM {$pre}ai_acc_events WHERE idempotency_key = %s LIMIT 1", $idempotency
            ) );
            if ( $existing ) {
                return [ 'success' => true, 'event_id' => $existing, 'id' => $existing, 'duplicate' => true, 'http' => 200 ];
            }
        }

        $ref_type = $args['reference_type'] ?? '';
        $ref_id   = (int) ( $args['reference_id'] ?? 0 );
        if ( ! $idempotency && $ref_type && $ref_id && self::has_column( 'ai_acc_events', 'reference_type' ) ) {
            $existing = (int) $wpdb->get_var( $wpdb->prepare(
                "SELECT id FROM {$pre}ai_acc_events WHERE reference_type = %s AND reference_id = %d AND event_type = %s ORDER BY id DESC LIMIT 1",
                $ref_type, $ref_id, $event_type
            ) );
            if ( $existing && empty( $args['allow_duplicate_ref'] ) ) {
                $st = (string) $wpdb->get_var( $wpdb->prepare(
                    "SELECT workflow_state FROM {$pre}ai_acc_events WHERE id = %d", $existing
                ) );
                if ( in_array( $st, [ 'pending', 'posted', 'confirmed', 'processing' ], true ) ) {
                    return [ 'success' => true, 'event_id' => $existing, 'id' => $existing, 'duplicate' => true, 'http' => 200 ];
                }
            }
        }

        $payload_json = wp_json_encode( $payload, JSON_UNESCAPED_UNICODE );
        $insert = [
            'event_type'     => $event_type,
            'workflow_state' => 'pending',
            'amount'         => $amount,
        ];
        if ( self::has_column( 'ai_acc_events', 'event_payload' ) ) {
            $insert['event_payload'] = $payload_json;
        }
        if ( self::has_column( 'ai_acc_events', 'metadata' ) ) {
            $insert['metadata'] = $payload_json;
        }
        if ( self::has_column( 'ai_acc_events', 'status' ) ) {
            $insert['status'] = 'PENDING';
        }
        if ( self::has_column( 'ai_acc_events', 'reference_type' ) && $ref_type ) {
            $insert['reference_type'] = $ref_type;
            $insert['reference_id']   = $ref_id;
        }
        if ( self::has_column( 'ai_acc_events', 'idempotency_key' ) && $idempotency ) {
            $insert['idempotency_key'] = $idempotency;
        }
        if ( self::has_column( 'ai_acc_events', 'dimension_id' ) && ! empty( $args['dimension_id'] ) ) {
            $insert['dimension_id'] = (int) $args['dimension_id'];
        }
        if ( self::has_column( 'ai_acc_events', 'source_module' ) && ! empty( $args['source_module'] ) ) {
            $insert['source_module'] = sanitize_text_field( $args['source_module'] );
        }
        if ( self::has_column( 'ai_acc_events', 'description' ) && ! empty( $args['description'] ) ) {
            $insert['description'] = sanitize_text_field( $args['description'] );
        }
        if ( self::has_column( 'ai_acc_events', 'created_by' ) ) {
            $insert['created_by'] = (int) ( $args['created_by'] ?? get_current_user_id() );
        }
        if ( self::has_column( 'ai_acc_events', 'reverses_event_id' ) && ! empty( $args['reverses_event_id'] ) ) {
            $insert['reverses_event_id'] = (int) $args['reverses_event_id'];
        }

        $wpdb->insert( $pre . 'ai_acc_events', $insert );
        $event_id = (int) $wpdb->insert_id;
        if ( ! $event_id ) {
            return [ 'success' => false, 'message' => $wpdb->last_error ?: 'خطا در ایجاد رویداد', 'http' => 500 ];
        }
        return [ 'success' => true, 'event_id' => $event_id, 'id' => $event_id, 'http' => 201 ];
    }

    /**
     * حل mapping: template → manual_entries → rules (legacy) → snapshot
     */
    public static function resolve_mapping( string $event_type, array $payload, ?array $ev = null ): array {
        global $wpdb;
        $pre = $wpdb->prefix;

        if ( $ev && ! empty( $ev['posted_mapping_json'] ) ) {
            $snap = json_decode( $ev['posted_mapping_json'], true );
            if ( is_array( $snap ) && ! empty( $snap['pairs'] ) ) {
                return [
                    'pairs'   => $snap['pairs'],
                    'source'  => 'snapshot',
                    'version' => $ev['template_version'] ?? null,
                ];
            }
        }

        $tpl = $wpdb->get_row( $wpdb->prepare(
            "SELECT version, mapping_logic FROM {$pre}ai_acc_event_templates
             WHERE event_type = %s AND is_active = 1 ORDER BY version DESC LIMIT 1",
            $event_type
        ), ARRAY_A );

        if ( $tpl ) {
            $mapping = is_string( $tpl['mapping_logic'] ) ? json_decode( $tpl['mapping_logic'], true ) : $tpl['mapping_logic'];
            if ( ! empty( $mapping['pairs'] ) ) {
                return [ 'pairs' => $mapping['pairs'], 'source' => 'template', 'version' => (int) $tpl['version'] ];
            }
        }

        if ( ! empty( $payload['manual_entries']['pairs'] ) ) {
            return [ 'pairs' => $payload['manual_entries']['pairs'], 'source' => 'manual', 'version' => null ];
        }

        $from_rules = self::rules_to_pairs( $event_type );
        if ( $from_rules ) {
            return $from_rules;
        }

        return [ 'pairs' => null, 'source' => 'none', 'version' => null ];
    }

    /** تبدیل ai_acc_rules قدیمی به pairs */
    public static function rules_to_pairs( string $event_type ): ?array {
        global $wpdb;
        $pre = $wpdb->prefix;
        if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $pre . 'ai_acc_rules' ) ) !== $pre . 'ai_acc_rules' ) {
            return null;
        }
        $rules = $wpdb->get_results( $wpdb->prepare(
            "SELECT * FROM {$pre}ai_acc_rules WHERE event_type = %s AND is_active = 1 ORDER BY priority ASC",
            $event_type
        ), ARRAY_A );
        if ( ! $rules ) {
            return null;
        }
        $pairs = [];
        foreach ( $rules as $r ) {
            $logic = json_decode( $r['dynamic_target_logic'] ?? '{}', true ) ?: [];
            $src   = $logic['source_field'] ?? $logic['amount_field'] ?? 'amount';
            $dr    = (int) ( $r['account_id'] ?? 0 );
            $cr    = (int) ( $r['target_account_id'] ?? 0 );
            $dir   = strtoupper( (string) ( $r['flow_direction'] ?? 'OUT' ) );
            if ( $dir === 'IN' || $dir === 'CREDIT' ) {
                $dr = (int) ( $r['source_account_id'] ?? $dr );
                $cr = (int) ( $r['account_id'] ?? $cr );
            }
            if ( $dr && $cr ) {
                $pairs[] = [
                    'dr_gl_id'     => $dr,
                    'cr_gl_id'     => $cr,
                    'source_field' => $src,
                    'desc'         => $event_type . ' (rule)',
                ];
            }
        }
        return $pairs ? [ 'pairs' => $pairs, 'source' => 'rules', 'version' => null ] : null;
    }

    public static function pair_amount( array $pair, array $payload, float $event_amount ): ?float {
        $src = $pair['source_field'] ?? null;
        if ( $src !== null && $src !== '' ) {
            return isset( $payload[ $src ] ) ? (float) $payload[ $src ] : null;
        }
        if ( isset( $pair['amount'] ) ) {
            return (float) $pair['amount'];
        }
        return $event_amount;
    }

    /** اعتبارسنجی تعادل: جمع بدهکار = جمع بستانکار در سطح رویداد */
    public static function validate_balance( array $resolved_pairs, array $payload, float $event_amount ): void {
        $total_dr = 0.0;
        $total_cr = 0.0;
        foreach ( $resolved_pairs as $item ) {
            $amt = (float) ( $item['amount'] ?? 0 );
            if ( $amt == 0.0 ) {
                continue;
            }
            $total_dr += $amt;
            $total_cr += $amt;
        }
        if ( $total_dr <= 0 ) {
            throw new \Exception( 'هیچ مبلغ معتبری برای پست یافت نشد' );
        }
        if ( abs( $total_dr - $total_cr ) > 0.01 ) {
            throw new \Exception( 'عدم تعادل سند: جمع بدهکار و بستانکار برابر نیست' );
        }
    }

    /**
     * نگاشت ابعاد چندگانه: pair.dimension_values | payload.dimensions | event.dimension_id
     * @return array{primary:?int,map:array<string,int>}
     */
    public static function resolve_dimensions( array $pair, array $ev, array $payload ): array {
        $map = [];
        if ( ! empty( $pair['dimension_values'] ) && is_array( $pair['dimension_values'] ) ) {
            foreach ( $pair['dimension_values'] as $k => $v ) {
                $map[ strtoupper( (string) $k ) ] = (int) $v;
            }
        }
        if ( ! empty( $payload['dimensions'] ) && is_array( $payload['dimensions'] ) ) {
            foreach ( $payload['dimensions'] as $k => $v ) {
                if ( is_numeric( $v ) ) {
                    $map[ strtoupper( (string) $k ) ] = (int) $v;
                }
            }
        }
        $primary = $pair['dimension_id'] ?? $ev['dimension_id'] ?? null;
        if ( $primary && empty( $map ) ) {
            $map['PRIMARY'] = (int) $primary;
        }
        if ( ! $primary && ! empty( $map ) ) {
            $primary = (int) reset( $map );
        }
        return [ 'primary' => $primary ? (int) $primary : null, 'map' => $map ];
    }

    /** اعتبارسنجی ابعاد الزامی per GL */
    public static function validate_gl_dimensions( int $gl_id, array $dim_map ): void {
        global $wpdb;
        $pre = $wpdb->prefix;
        if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $pre . 'ai_acc_gl_dim_rules' ) ) !== $pre . 'ai_acc_gl_dim_rules' ) {
            return;
        }
        $required = $wpdb->get_results( $wpdb->prepare(
            "SELECT g.group_code, g.id AS group_id
             FROM {$pre}ai_acc_gl_dim_rules r
             JOIN {$pre}ai_acc_dimension_groups g ON g.id = r.group_id
             WHERE r.gl_id = %d AND r.is_required = 1",
            $gl_id
        ), ARRAY_A ) ?: [];
        foreach ( $required as $req ) {
            $code = strtoupper( (string) $req['group_code'] );
            $found = false;
            foreach ( $dim_map as $k => $vid ) {
                if ( strtoupper( (string) $k ) === $code || $k === 'PRIMARY' ) {
                    if ( $vid > 0 ) {
                        $found = true;
                        break;
                    }
                }
            }
            if ( ! $found ) {
                $val_in_group = false;
                foreach ( $dim_map as $vid ) {
                    if ( $vid <= 0 ) continue;
                    $gid = (int) $wpdb->get_var( $wpdb->prepare(
                        "SELECT d.group_id FROM {$pre}ai_acc_dimension_values v
                         JOIN {$pre}ai_acc_dimensions d ON d.id = v.dim_id
                         WHERE v.id = %d LIMIT 1",
                        $vid
                    ) );
                    if ( $gid === (int) $req['group_id'] ) {
                        $val_in_group = true;
                        break;
                    }
                }
                if ( ! $val_in_group ) {
                    throw new \Exception( "بُعد الزامی «{$code}» برای حساب #{$gl_id} مقداردهی نشده است" );
                }
            }
        }
    }

    /**
     * پست رویداد — هسته واحد ERP
     */
    public static function post_event( int $id ): array {
        global $wpdb;
        $pre = $wpdb->prefix;

        $cols = [ 'id', 'event_type', 'metadata', 'event_payload', 'workflow_state', 'amount' ];
        foreach ( [ 'dimension_id', 'posted_mapping_json', 'template_version' ] as $c ) {
            if ( self::has_column( 'ai_acc_events', $c ) ) {
                $cols[] = $c;
            }
        }
        $ev = $wpdb->get_row( $wpdb->prepare(
            'SELECT ' . implode( ', ', $cols ) . " FROM {$pre}ai_acc_events WHERE id = %d", $id
        ), ARRAY_A );
        if ( ! $ev ) {
            return [ 'success' => false, 'message' => "رویداد #{$id} یافت نشد", 'http' => 404 ];
        }

        $state = $ev['workflow_state'] ?? 'pending';
        if ( $state === 'posted' || $state === 'confirmed' ) {
            return [ 'success' => false, 'message' => 'این رویداد قبلاً پست شده است', 'http' => 400 ];
        }
        if ( ! in_array( $state, [ 'pending', 'failed' ], true ) ) {
            return [ 'success' => false, 'message' => "وضعیت غیرمجاز برای پست: {$state}", 'http' => 400 ];
        }

        $raw = $ev['metadata'] ?? $ev['event_payload'] ?? '{}';
        $payload = json_decode( $raw ?: '{}', true );
        if ( ! is_array( $payload ) ) {
            $payload = [];
        }

        $resolved = self::resolve_mapping( $ev['event_type'], $payload, $ev );
        $pairs    = $resolved['pairs'] ?? null;
        $source   = $resolved['source'] ?? 'none';
        $tpl_ver  = $resolved['version'] ?? null;

        if ( ! $pairs ) {
            $msg = "هیچ mapping برای «{$ev['event_type']}» یافت نشد (template/manual/rules)";
            $wpdb->update( $pre . 'ai_acc_events', [ 'workflow_state' => 'failed', 'error_message' => $msg ], [ 'id' => $id ] );
            return [ 'success' => false, 'message' => $msg, 'http' => 400 ];
        }

        $resolved_lines = [];
        foreach ( $pairs as $idx => $p ) {
            $amt = self::pair_amount( $p, $payload, (float) $ev['amount'] );
            if ( $amt === null || abs( $amt ) < 0.0001 ) {
                continue;
            }
            $resolved_lines[] = array_merge( $p, [ 'amount' => $amt, 'pair_idx' => $idx ] );
        }

        try {
            self::validate_balance( $resolved_lines, $payload, (float) $ev['amount'] );
        } catch ( \Throwable $e ) {
            $wpdb->update( $pre . 'ai_acc_events', [ 'workflow_state' => 'failed', 'error_message' => $e->getMessage() ], [ 'id' => $id ] );
            return [ 'success' => false, 'message' => $e->getMessage(), 'http' => 400 ];
        }

        $snapshot = [
            'pairs'        => $resolved_lines,
            'source'       => $source,
            'posted_at'    => current_time( 'mysql' ),
            'event_type'   => $ev['event_type'],
            'payload_hash' => md5( wp_json_encode( $payload ) ),
        ];

        $has_flow_dim   = self::has_column( 'ai_acc_flows', 'dimension_id' );
        $has_entry_pair = self::has_column( 'ai_acc_flows', 'entry_pair_id' );
        $has_tag_json   = self::has_column( 'ai_acc_flows', 'tag_json' );

        $wpdb->query( 'START TRANSACTION' );
        try {
            $wpdb->query( $wpdb->prepare(
                "UPDATE {$pre}ai_acc_events SET workflow_state='processing', error_message=NULL WHERE id=%d", $id
            ) );
            $wpdb->query( $wpdb->prepare(
                "DELETE FROM {$pre}ai_acc_flows WHERE event_id=%d AND is_confirmed=0", $id
            ) );

            $entry_pair_id = 'EP' . $id . '-' . (string) round( microtime( true ) * 1000 );
            $inserted = 0;
            $line = 0;

            foreach ( $resolved_lines as $item ) {
                $line++;
                $dr   = (int) ( $item['dr_gl_id'] ?? 0 );
                $cr   = (int) ( $item['cr_gl_id'] ?? 0 );
                $amt  = (float) $item['amount'];
                $desc = $item['desc'] ?? $item['description'] ?? $ev['event_type'];

                if ( ! $dr || ! $wpdb->get_var( $wpdb->prepare( "SELECT 1 FROM {$pre}ai_acc_accounts WHERE account_id=%d", $dr ) ) ) {
                    throw new \Exception( "حساب بدهکار نامعتبر: #{$dr}" );
                }
                if ( ! $cr || ! $wpdb->get_var( $wpdb->prepare( "SELECT 1 FROM {$pre}ai_acc_accounts WHERE account_id=%d", $cr ) ) ) {
                    throw new \Exception( "حساب بستانکار نامعتبر: #{$cr}" );
                }

                $dims = self::resolve_dimensions( $item, $ev, $payload );
                self::validate_gl_dimensions( $dr, $dims['map'] );
                self::validate_gl_dimensions( $cr, $dims['map'] );

                $flow_row = [
                    'event_id'        => $id,
                    'from_account_id' => $dr,
                    'to_account_id'   => $cr,
                    'amount'          => $amt,
                    'description'     => mb_substr( (string) $desc, 0, 500 ),
                    'is_confirmed'    => 0,
                ];
                if ( $has_flow_dim ) {
                    $flow_row['dimension_id'] = $dims['primary'];
                }
                if ( $has_entry_pair ) {
                    $flow_row['entry_pair_id'] = $entry_pair_id;
                    $flow_row['line_no']       = $line;
                }
                if ( $has_tag_json ) {
                    $flow_row['tag_json'] = wp_json_encode( array_filter( [
                        'source'            => $source,
                        'pair_idx'          => $item['pair_idx'] ?? $line - 1,
                        'dimension_values'  => $dims['map'] ?: null,
                        'source_module'     => $payload['source_module'] ?? null,
                        'service_office'    => $payload['service_office_code'] ?? null,
                    ] ), JSON_UNESCAPED_UNICODE );
                }
                $wpdb->insert( $pre . 'ai_acc_flows', $flow_row );
                if ( $wpdb->last_error ) {
                    throw new \Exception( $wpdb->last_error );
                }
                $inserted++;
            }

            if ( $inserted === 0 ) {
                throw new \Exception( 'هیچ آرتیکل معتبری تولید نشد' );
            }

            $update_ev = [
                'workflow_state'   => 'posted',
                'posted_at'        => current_time( 'mysql' ),
                'template_version' => $tpl_ver,
                'error_message'    => null,
            ];
            if ( self::has_column( 'ai_acc_events', 'posted_mapping_json' ) ) {
                $update_ev['posted_mapping_json'] = wp_json_encode( $snapshot, JSON_UNESCAPED_UNICODE );
            }
            $wpdb->update( $pre . 'ai_acc_events', $update_ev, [ 'id' => $id ] );

            $wpdb->query( 'COMMIT' );
            return [
                'success'  => true,
                'message'  => "{$inserted} آرتیکل ثبت شد (منبع: {$source})",
                'flows'    => $inserted,
                'snapshot' => $snapshot,
                'http'     => 200,
            ];
        } catch ( \Throwable $e ) {
            $wpdb->query( 'ROLLBACK' );
            $wpdb->update( $pre . 'ai_acc_events', [
                'workflow_state' => 'failed',
                'error_message'  => $e->getMessage(),
            ], [ 'id' => $id ] );
            return [ 'success' => false, 'message' => $e->getMessage(), 'http' => 400 ];
        }
    }

    /**
     * سند برگشتی (storno) برای رویداد posted/confirmed
     */
    public static function create_reversal( int $original_id, int $user_id = 0 ): array {
        global $wpdb;
        $pre = $wpdb->prefix;

        $orig = $wpdb->get_row( $wpdb->prepare(
            "SELECT * FROM {$pre}ai_acc_events WHERE id = %d", $original_id
        ), ARRAY_A );
        if ( ! $orig ) {
            return [ 'success' => false, 'message' => 'رویداد مبدأ یافت نشد', 'http' => 404 ];
        }
        if ( ! in_array( $orig['workflow_state'] ?? '', [ 'posted', 'confirmed' ], true ) ) {
            return [ 'success' => false, 'message' => 'فقط رویداد پست‌شده/تأییدشده قابل برگشت است', 'http' => 400 ];
        }

        if ( self::has_column( 'ai_acc_events', 'reversed_by_event_id' ) && ! empty( $orig['reversed_by_event_id'] ) ) {
            return [ 'success' => false, 'message' => 'قبلاً برگشت خورده — رویداد #' . (int) $orig['reversed_by_event_id'], 'http' => 400 ];
        }

        $snapshot = json_decode( $orig['posted_mapping_json'] ?? '', true );
        if ( empty( $snapshot['pairs'] ) ) {
            $flows = $wpdb->get_results( $wpdb->prepare(
                "SELECT from_account_id, to_account_id, amount, description FROM {$pre}ai_acc_flows WHERE event_id = %d ORDER BY line_no ASC",
                $original_id
            ), ARRAY_A ) ?: [];
            $pairs = [];
            foreach ( $flows as $f ) {
                $pairs[] = [
                    'dr_gl_id' => (int) $f['to_account_id'],
                    'cr_gl_id' => (int) $f['from_account_id'],
                    'amount'   => (float) $f['amount'],
                    'desc'     => 'برگشت: ' . ( $f['description'] ?? '' ),
                ];
            }
            $snapshot = [ 'pairs' => $pairs, 'source' => 'reversal' ];
        } else {
            $rev_pairs = [];
            foreach ( $snapshot['pairs'] as $p ) {
                $rev_pairs[] = [
                    'dr_gl_id' => (int) ( $p['cr_gl_id'] ?? 0 ),
                    'cr_gl_id' => (int) ( $p['dr_gl_id'] ?? 0 ),
                    'amount'   => (float) ( $p['amount'] ?? 0 ),
                    'desc'     => 'برگشت: ' . ( $p['desc'] ?? $p['description'] ?? '' ),
                    'dimension_values' => $p['dimension_values'] ?? null,
                ];
            }
            $snapshot['pairs'] = $rev_pairs;
        }

        if ( empty( $snapshot['pairs'] ) ) {
            return [ 'success' => false, 'message' => 'آرتیکل برای برگشت یافت نشد', 'http' => 400 ];
        }

        $payload_orig = json_decode( $orig['metadata'] ?? $orig['event_payload'] ?? '{}', true ) ?: [];
        $payload = array_merge( $payload_orig, [
            'manual_entries'    => [ 'pairs' => $snapshot['pairs'] ],
            'reversal_of'       => $original_id,
            'reversal_reason'   => 'storno',
        ] );

        $created = self::create_event( [
            'event_type'        => 'REVERSAL',
            'amount'            => (float) $orig['amount'],
            'payload'           => $payload,
            'reference_type'    => 'reversal',
            'reference_id'      => $original_id,
            'idempotency_key'   => 'REV_' . $original_id,
            'reverses_event_id' => $original_id,
            'description'       => 'برگشت رویداد #' . $original_id,
            'created_by'        => $user_id ?: get_current_user_id(),
            'allow_duplicate_ref' => false,
        ] );
        if ( ! $created['success'] ) {
            return $created;
        }

        $rev_id = (int) $created['event_id'];
        $post   = self::post_event( $rev_id );
        if ( ! $post['success'] ) {
            return $post;
        }

        if ( self::has_column( 'ai_acc_events', 'reversed_by_event_id' ) ) {
            $wpdb->update( $pre . 'ai_acc_events', [ 'reversed_by_event_id' => $rev_id ], [ 'id' => $original_id ] );
        }

        return [
            'success'           => true,
            'reversal_event_id' => $rev_id,
            'original_event_id' => $original_id,
            'message'           => 'سند برگشتی #' . $rev_id . ' صادر شد',
            'http'              => 200,
        ];
    }

    /** کمک‌کننده: create + post یکجا */
    public static function emit_and_post( array $args ): array {
        $created = self::create_event( $args );
        if ( ! $created['success'] ) {
            return $created;
        }
        $post = self::post_event( (int) $created['event_id'] );
        $post['event_id'] = (int) $created['event_id'];
        $post['duplicate'] = ! empty( $created['duplicate'] );
        return $post;
    }
}
