<?php
/**
 * Acc_Portal_Accounting — حسابداری مجزا per-portal با seed خودکار سرفصل‌ها
 * هر پورتال: دفتر کل · اسناد · تراز · سود و زیان — فیلتر source_module
 */
if ( ! defined( 'ABSPATH' ) ) exit;

class Acc_Portal_Accounting {

    /** تعریف پورتال‌ها — سرفصل‌ها و GL به‌صورت خودکار نصب می‌شوند */
    public static function portal_defs(): array {
        return [
            'arag_shop' => [
                'module'           => 'arag_shop',
                'ledger_tag'       => 'SHOP',
                'office_code'      => 'ARAG-01',
                'office_name'      => 'فروشگاه عرقیات',
                'subsystem_label'  => 'حسابداری فروش فروشگاه',
                'description'      => 'دفتر کل · اسناد · تراز · سود و زیان — فروش آنلاین عرقیات',
                'event_types'      => [ 'SHOP_SALE' ],
                'reference_types'  => [ 'arag_order' ],
                'option_key'       => 'arag_portal_office',
                'gl'               => [
                    'bank'          => 14,
                    'ar'            => 21,
                    'inventory'     => 13,
                    'revenue_goods' => 51,
                    'cogs'          => 61,
                ],
                'chart' => self::_shop_chart_seed(),
            ],
            'service_portal' => [
                'module'           => 'service_portal',
                'ledger_tag'       => 'SERVICE',
                'office_code'      => 'HVAC-01',
                'office_name'      => 'خدمات سردخانه و چیلر',
                'subsystem_label'  => 'حسابداری پورتال خدمات',
                'description'      => 'دفتر کل · اسناد · بهای تمام‌شده — پورتال خدمات HVAC',
                'event_types'      => [ 'SERVICE_RECEIPT', 'SERVICE_SUPPLIER_COST', 'SERVICE_INVOICE', 'SERVICE_SPLIT' ],
                'reference_types'  => [ 'svc_report', 'svc_split' ],
                'option_key'       => 'svc_portal_office',
                'gl'               => [
                    'bank'            => 14,
                    'ar'              => 21,
                    'revenue_service' => 52,
                    'svc_cogs'        => 65,
                    'svc_reward_exp'  => 66,
                    'svc_tech_fee'    => 67,
                    'svc_senior_fee'  => 68,
                    'svc_overhead'    => 69,
                    'svc_supplier_ap' => 70,
                ],
                'chart' => self::_service_chart_seed(),
            ],
            'election' => [
                'module'           => 'election',
                'ledger_tag'       => 'ELECTION',
                'office_code'      => 'ELC-HQ-01',
                'office_name'      => 'ستاد انتخابات — هواداران',
                'subsystem_label'  => 'حسابداری کمپین انتخابات',
                'description'      => 'دفتر کل · اسناد · تراز · بودجه کمپین — پورتال هواداران',
                'event_types'      => [ 'ELECTION_RECEIPT', 'ELECTION_EXPENSE' ],
                'reference_types'  => [ 'election_transaction' ],
                'option_key'       => 'election_portal_office',
                'gl'               => [
                    'bank'            => 3113,
                    'petty_cash'      => 3120,
                    'revenue_income'  => 3141,
                    'expense_media'   => 3151,
                    'expense_field'   => 3152,
                    'expense_goods'   => 3153,
                    'expense_staff'   => 3154,
                    'expense_logistics' => 3155,
                    'expense_tech'    => 3156,
                    'expense_other'   => 3159,
                ],
                'chart' => self::_election_chart_seed(),
            ],
            'education' => [
                'module'           => 'education',
                'ledger_tag'       => 'EDU',
                'office_code'      => 'EDU-HQ-01',
                'office_name'      => 'ACC EduBrain — آموزش',
                'subsystem_label'  => 'حسابداری پورتال آموزش',
                'description'      => 'دفتر کل · بهای تمام‌شده · حق‌الزحمه مربیان — EduBrain',
                'event_types'      => [ 'EDU_SESSION_INVOICE' ],
                'reference_types'  => [ 'edu_session' ],
                'option_key'       => 'education_portal_office',
                'gl'               => [
                    'bank'              => 4113,
                    'ar'                => 4121,
                    'revenue_academic'  => 4141,
                    'revenue_skill'     => 4142,
                    'mentor_expense'    => 4151,
                    'transport_expense' => 4152,
                    'materials_expense' => 4153,
                    'overhead_expense'  => 4154,
                    'venue_expense'     => 4155,
                    'mentor_payable'    => 4161,
                    'org_payable'       => 4162,
                    'transport_payable' => 4163,
                    'platform_revenue'  => 4164,
                ],
                'chart' => self::_education_chart_seed(),
            ],
            /* Stub MVP — فاکتور کامل در فاز بعد؛ فیلد invoice_status روی growth_orders */
            'growth_portal' => [
                'module'           => 'growth_portal',
                'ledger_tag'       => 'GROWTH',
                'office_code'      => 'GRW-01',
                'office_name'      => 'رشدگاه ACC — خدمات و سفیران',
                'subsystem_label'  => 'حسابداری پورتال رشدگاه (stub)',
                'description'      => 'Stub MVP — درآمد خدمات و کمیسیون سفیر؛ صدور سند در فاز بعد',
                'event_types'      => [ 'GROWTH_SERVICE_INVOICE', 'GROWTH_COMMISSION_PAYOUT' ],
                'reference_types'  => [ 'growth_order', 'growth_commission' ],
                'option_key'       => 'growth_portal_office',
                'gl'               => [
                    'bank'               => 5113,
                    'ar'                 => 5121,
                    'revenue_service'    => 5141,
                    'commission_expense' => 5151,
                    'commission_payable' => 5161,
                ],
                'chart' => self::_growth_chart_seed(),
            ],
        ];
    }

    private static function _growth_chart_seed(): array {
        return [
            [ 5100, 'GR-1', 'دارایی — رشدگاه', 'asset', null, false ],
            [ 5110, 'GR-11', 'نقد و بانک', 'asset', 5100, false ],
            [ 5113, 'GR-1113', 'حساب بانکی رشدگاه', 'asset', 5110, true ],
            [ 5120, 'GR-12', 'دریافتنی خدمات', 'asset', 5100, false ],
            [ 5121, 'GR-1121', 'دریافتنی — مشتریان رشدگاه', 'asset', 5120, true ],
            [ 5140, 'GR-4', 'درآمد خدمات رشدگاه', 'revenue', null, false ],
            [ 5141, 'GR-4110', 'درآمد ساخت سایت / برند / آموزش', 'revenue', 5140, true ],
            [ 5150, 'GR-5', 'هزینه کمیسیون سفیران', 'expense', null, false ],
            [ 5151, 'GR-5110', 'هزینه کمیسیون ارجاع', 'expense', 5150, true ],
            [ 5160, 'GR-2', 'بدهی — کمیسیون', 'liability', null, false ],
            [ 5161, 'GR-2110', 'پرداختنی سفیران', 'liability', 5160, true ],
        ];
    }

    private static function _shop_chart_seed(): array {
        return [
            [ 1100, 'SH-1', 'دارایی‌های جاری — فروشگاه', 'asset', null, false ],
            [ 1101, 'SH-11', 'موجودی نقد و بانک', 'asset', 1100, false ],
            [ 14,   'SH-1113', 'حساب بانکی جاری فروشگاه', 'asset', 1101, true ],
            [ 1102, 'SH-12', 'حساب‌های دریافتنی', 'asset', 1100, false ],
            [ 21,   'SH-1121', 'دریافتنی — مشتریان فروشگاه', 'asset', 1102, true ],
            [ 1103, 'SH-13', 'موجودی کالا', 'asset', 1100, false ],
            [ 13,   'SH-1130', 'موجودی عرقیات', 'asset', 1103, true ],
            [ 1150, 'SH-4', 'درآمد عملیاتی — فروشگاه', 'revenue', null, false ],
            [ 51,   'SH-4110', 'درآمد فروش کالا', 'revenue', 1150, true ],
            [ 1160, 'SH-5', 'هزینه عملیاتی — فروشگاه', 'expense', null, false ],
            [ 61,   'SH-5110', 'بهای تمام‌شده فروش', 'expense', 1160, true ],
        ];
    }

    private static function _service_chart_seed(): array {
        return [
            [ 2100, 'SV-1', 'دارایی — خدمات HVAC', 'asset', null, false ],
            [ 2101, 'SV-11', 'نقد و بانک', 'asset', 2100, false ],
            [ 14,   'SV-1113', 'حساب بانکی خدمات', 'asset', 2101, true ],
            [ 2102, 'SV-12', 'دریافتنی خدمات', 'asset', 2100, false ],
            [ 21,   'SV-1121', 'دریافتنی — مشتریان خدمات', 'asset', 2102, true ],
            [ 2150, 'SV-4', 'درآمد خدمات', 'revenue', null, false ],
            [ 52,   'SV-4120', 'درآمد ارائه خدمات', 'revenue', 2150, true ],
            [ 2160, 'SV-5', 'هزینه و بهای تمام‌شده خدمات', 'expense', null, false ],
            [ 65,   'SV-5151', 'بهای تمام‌شده خدمات', 'expense', 2160, true ],
            [ 66,   'SV-5152', 'هزینه پاداش مشتری', 'expense', 2160, true ],
            [ 67,   'SV-5153', 'حق‌الزحمه کارشناس', 'expense', 2160, true ],
            [ 68,   'SV-5154', 'سهم کارشناس ارشد', 'expense', 2160, true ],
            [ 69,   'SV-5155', 'سربار انرژی و تعمیرگاه', 'expense', 2160, true ],
            [ 2170, 'SV-2', 'بدهی — خدمات', 'liability', null, false ],
            [ 70,   'SV-2112', 'پرداختنی تأمین‌کنندگان خدمات', 'liability', 2170, true ],
        ];
    }

    private static function _education_chart_seed(): array {
        return [
            [ 4100, 'ED-1', 'دارایی — آموزش EduBrain', 'asset', null, false ],
            [ 4110, 'ED-11', 'نقد و بانک', 'asset', 4100, false ],
            [ 4113, 'ED-1113', 'حساب بانکی EduBrain', 'asset', 4110, true ],
            [ 4120, 'ED-12', 'حساب‌های دریافتنی', 'asset', 4100, false ],
            [ 4121, 'ED-1121', 'دریافتنی — دانش‌آموزان', 'asset', 4120, true ],
            [ 4140, 'ED-4', 'درآمد عملیاتی — آموزش', 'revenue', null, false ],
            [ 4141, 'ED-4110', 'درآمد جلسات تحصیلی', 'revenue', 4140, true ],
            [ 4142, 'ED-4111', 'درآمد جلسات مهارتی', 'revenue', 4140, true ],
            [ 4164, 'ED-4120', 'کارمزد پلتفرم ACC Edu', 'revenue', 4140, true ],
            [ 4150, 'ED-5', 'بهای تمام‌شده و هزینه آموزش', 'expense', null, false ],
            [ 4151, 'ED-5151', 'حق‌الزحمه مربی/معلم', 'expense', 4150, true ],
            [ 4152, 'ED-5152', 'هزینه ایاب‌ذهاب', 'expense', 4150, true ],
            [ 4153, 'ED-5153', 'مواد آموزشی و کاغذ', 'expense', 4150, true ],
            [ 4154, 'ED-5154', 'سربار اداری آموزش', 'expense', 4150, true ],
            [ 4155, 'ED-5155', 'اجاره فضای آموزشی', 'expense', 4150, true ],
            [ 4160, 'ED-2', 'بدهی — آموزش', 'liability', null, false ],
            [ 4161, 'ED-2111', 'پرداختنی مربیان', 'liability', 4160, true ],
            [ 4162, 'ED-2112', 'پرداختنی مؤسسات/مدارس', 'liability', 4160, true ],
            [ 4163, 'ED-2113', 'پرداختنی ایاب‌ذهاب', 'liability', 4160, true ],
        ];
    }

    private static function _election_chart_seed(): array {
        return [
            [ 3100, 'EL-1', 'دارایی — کمپین انتخابات', 'asset', null, false ],
            [ 3110, 'EL-11', 'نقد و بانک ستاد', 'asset', 3100, false ],
            [ 3113, 'EL-1113', 'حساب بانکی کمپین', 'asset', 3110, true ],
            [ 3120, 'EL-1120', 'تنخواه میدانی', 'asset', 3100, true ],
            [ 3140, 'EL-4', 'درآمد کمپین', 'revenue', null, false ],
            [ 3141, 'EL-4110', 'درآمد — کمک و حمایت مالی', 'revenue', 3140, true ],
            [ 3150, 'EL-5', 'هزینه‌های کمپین', 'expense', null, false ],
            [ 3151, 'EL-5111', 'هزینه تبلیغات و رسانه', 'expense', 3150, true ],
            [ 3152, 'EL-5112', 'هزینه عملیات میدانی', 'expense', 3150, true ],
            [ 3153, 'EL-5113', 'هزینه اقلام تبلیغاتی', 'expense', 3150, true ],
            [ 3154, 'EL-5114', 'هزینه نیروی انسانی', 'expense', 3150, true ],
            [ 3155, 'EL-5115', 'هزینه اداری و لجستیک', 'expense', 3150, true ],
            [ 3156, 'EL-5116', 'هزینه فناوری و GIS', 'expense', 3150, true ],
            [ 3159, 'EL-5119', 'سایر هزینه‌های کمپین', 'expense', 3150, true ],
        ];
    }

    public static function get_portal( string $module ): ?array {
        $defs = self::portal_defs();
        return $defs[ $module ] ?? null;
    }

    public static function list_portals(): array {
        $out = [];
        foreach ( self::portal_defs() as $key => $def ) {
            $portal = self::portal_settings( $key );
            $out[] = [
                'key'             => $key,
                'module'          => $def['module'],
                'ledger_tag'      => $portal['ledger_tag'] ?? $def['ledger_tag'],
                'office_name'     => $portal['office_name'] ?? $def['office_name'],
                'subsystem_label' => $def['subsystem_label'],
                'description'     => $def['description'],
                'page'            => self::portal_page( $key ),
            ];
        }
        return $out;
    }

    public static function portal_page( string $module ): string {
        $pages = [
            'arag_shop'      => 'shop-accounting.php',
            'service_portal' => 'portal-accounting.php?module=service_portal',
            'election'       => 'election-accounting.php',
            'education'      => 'education-accounting.php',
        ];
        $base = defined( 'ACC_URL' ) ? ACC_URL : '/acc-engine/';
        return $base . ( $pages[ $module ] ?? 'portal-accounting.php?module=' . $module );
    }

    public static function portal_settings( string $module ): array {
        $def = self::get_portal( $module );
        if ( ! $def ) {
            return [];
        }
        $base = [
            'office_code'     => $def['office_code'],
            'office_name'     => $def['office_name'],
            'subsystem_label' => $def['subsystem_label'],
            'ledger_tag'      => $def['ledger_tag'],
        ];
        $key = $def['option_key'] ?? '';
        if ( ! $key ) {
            return $base;
        }
        $saved = get_option( $key, [] );
        return array_merge( $base, is_array( $saved ) ? $saved : [] );
    }

    public static function save_portal_settings( string $module, array $data ): bool {
        $def = self::get_portal( $module );
        if ( ! $def || empty( $def['option_key'] ) ) {
            return false;
        }
        $base = self::portal_settings( $module );
        $out  = [];
        foreach ( [ 'office_code', 'office_name', 'subsystem_label', 'ledger_tag' ] as $k ) {
            if ( isset( $data[ $k ] ) ) {
                $out[ $k ] = sanitize_text_field( (string) $data[ $k ] );
            }
        }
        return update_option( $def['option_key'], array_merge( $base, $out ), false );
    }

    public static function meta( string $module ): array {
        $def    = self::get_portal( $module );
        $portal = self::portal_settings( $module );
        if ( ! $def ) {
            return [];
        }
        return [
            'module'          => $def['module'],
            'event_types'     => $def['event_types'],
            'ledger_tag'      => $portal['ledger_tag'] ?? $def['ledger_tag'],
            'office_code'     => $portal['office_code'] ?? $def['office_code'],
            'office_name'     => $portal['office_name'] ?? $def['office_name'],
            'subsystem_label' => $def['subsystem_label'],
            'description'     => $def['description'],
        ];
    }

    public static function has_col( string $table, string $col ): bool {
        if ( ! class_exists( 'Acc_Event_Engine' ) ) {
            require_once ACC_PATH . 'includes/helpers/acc-event-engine.php';
        }
        return Acc_Event_Engine::has_column( $table, $col );
    }

    /** نصب/به‌روزرسانی سرفصل‌های یک پورتال */
    public static function ensure_chart( string $module ): array {
        global $wpdb;
        $def = self::get_portal( $module );
        if ( ! $def ) {
            return [ 'success' => false, 'message' => 'پورتال ناشناخته' ];
        }
        $pre = $wpdb->prefix;
        $t   = $pre . 'ai_acc_accounts';
        $created = 0;
        $updated = 0;

        foreach ( $def['chart'] as $row ) {
            [ $id, $code, $name, $type, $parent, $is_gl ] = $row;
            $exists = (int) $wpdb->get_var( $wpdb->prepare(
                "SELECT id FROM {$t} WHERE id = %d OR account_id = %d LIMIT 1", $id, $id
            ) );
            $data = [
                'account_code'  => $code,
                'account_name'  => $name,
                'account_type'  => $type,
                'parent_id'     => $parent,
                'is_system'     => 1,
                'dimension_type'=> 'NONE',
            ];
            if ( self::has_col( 'ai_acc_accounts', 'source_module' ) ) {
                $data['source_module'] = $module;
            }
            if ( $exists ) {
                $wpdb->update( $t, $data, [ 'id' => $exists ] );
                $wpdb->update( $t, [ 'account_id' => $id ], [ 'id' => $exists ] );
                $updated++;
            } else {
                $wpdb->insert( $t, array_merge( $data, [ 'id' => $id, 'account_id' => $id ] ) );
                $created++;
            }
        }
        return [ 'success' => true, 'module' => $module, 'created' => $created, 'updated' => $updated ];
    }

    public static function ensure_all_charts(): array {
        $results = [];
        foreach ( array_keys( self::portal_defs() ) as $mod ) {
            $results[ $mod ] = self::ensure_chart( $mod );
        }
        return $results;
    }

    /** فیلتر SQL رویدادهای یک پورتال */
    public static function event_where( string $module, string $alias = 'e' ): string {
        $def = self::get_portal( $module );
        if ( ! $def ) {
            return '1=0';
        }
        $parts = [];
        if ( ! empty( $def['event_types'] ) ) {
            $et = array_map( function ( $t ) {
                return "'" . esc_sql( $t ) . "'";
            }, $def['event_types'] );
            $parts[] = "{$alias}.event_type IN (" . implode( ',', $et ) . ")";
        }
        if ( ! empty( $def['reference_types'] ) ) {
            $rt = array_map( function ( $t ) {
                return "'" . esc_sql( $t ) . "'";
            }, $def['reference_types'] );
            $parts[] = "{$alias}.reference_type IN (" . implode( ',', $rt ) . ")";
        }
        if ( self::has_col( 'ai_acc_events', 'source_module' ) ) {
            $parts[] = "{$alias}.source_module = '" . esc_sql( $module ) . "'";
        }
        return '(' . implode( ' OR ', $parts ) . ')';
    }

    public static function chart_tree( string $module ): array {
        global $wpdb;
        $def = self::get_portal( $module );
        if ( ! $def ) {
            return [];
        }
        $pre = $wpdb->prefix;
        $t   = $pre . 'ai_acc_accounts';
        $ids = array_column( $def['chart'], 0 );

        if ( self::has_col( 'ai_acc_accounts', 'source_module' ) ) {
            $rows = $wpdb->get_results( $wpdb->prepare(
                "SELECT account_id AS id, account_code AS code, account_name AS name, account_type, parent_id, source_module,
                        (SELECT COUNT(*) FROM {$t} c WHERE c.parent_id = a.account_id) AS child_count
                 FROM {$t} a WHERE a.source_module = %s ORDER BY account_code",
                $module
            ), ARRAY_A ) ?: [];
            if ( $rows ) {
                return $rows;
            }
        }

        if ( ! $ids ) {
            return [];
        }
        $ph = implode( ',', array_fill( 0, count( $ids ), '%d' ) );
        return $wpdb->get_results( $wpdb->prepare(
            "SELECT account_id AS id, account_code AS code, account_name AS name, account_type, parent_id,
                    (SELECT COUNT(*) FROM {$t} c WHERE c.parent_id = a.account_id) AS child_count
             FROM {$t} a WHERE a.account_id IN ({$ph}) ORDER BY account_code",
            ...$ids
        ), ARRAY_A ) ?: [];
    }

    public static function gl_ids( string $module ): array {
        $def = self::get_portal( $module );
        return $def['gl'] ?? [];
    }

    private static function _account_balances( string $module, ?string $from, ?string $to, bool $confirmed_only ): array {
        global $wpdb;
        $pre   = $wpdb->prefix;
        $conds = [ self::event_where( $module, 'e' ) ];
        if ( $confirmed_only ) {
            $conds[] = 'f.is_confirmed = 1';
        }
        $args = [];
        if ( $from ) {
            $conds[] = 'f.created_at >= %s';
            $args[]  = sanitize_text_field( $from ) . ' 00:00:00';
        }
        if ( $to ) {
            $conds[] = 'f.created_at <= %s';
            $args[]  = sanitize_text_field( $to ) . ' 23:59:59';
        }
        $fwhere = implode( ' AND ', $conds );

        $sql = "SELECT a.account_id AS id, a.account_code AS code, a.account_name AS name, a.account_type,
                       COALESCE(SUM(CASE WHEN f.from_account_id = a.account_id THEN f.amount ELSE 0 END), 0) AS total_debit,
                       COALESCE(SUM(CASE WHEN f.to_account_id   = a.account_id THEN f.amount ELSE 0 END), 0) AS total_credit
                FROM {$pre}ai_acc_accounts a
                INNER JOIN {$pre}ai_acc_flows f
                    ON (f.from_account_id = a.account_id OR f.to_account_id = a.account_id)
                INNER JOIN {$pre}ai_acc_events e ON e.id = f.event_id AND {$fwhere}
                GROUP BY a.account_id, a.account_code, a.account_name, a.account_type
                HAVING total_debit <> 0 OR total_credit <> 0
                ORDER BY a.account_code ASC";

        return $args
            ? ( $wpdb->get_results( $wpdb->prepare( $sql, ...$args ), ARRAY_A ) ?: [] )
            : ( $wpdb->get_results( $sql, ARRAY_A ) ?: [] );
    }

    public static function dashboard( string $module ): array {
        global $wpdb;
        $pre   = $wpdb->prefix;
        $where = self::event_where( $module, 'e' );
        $meta  = self::meta( $module );

        $pending = (int) $wpdb->get_var(
            "SELECT COUNT(*) FROM {$pre}ai_acc_events e WHERE {$where} AND e.workflow_state IN ('pending','failed')"
        );
        $posted = (int) $wpdb->get_var(
            "SELECT COUNT(*) FROM {$pre}ai_acc_events e WHERE {$where} AND e.workflow_state = 'posted'"
        );
        $confirmed = (int) $wpdb->get_var(
            "SELECT COUNT(*) FROM {$pre}ai_acc_events e WHERE {$where} AND e.workflow_state = 'confirmed'"
        );

        $month_start = date( 'Y-m-01' );
        $month_end   = date( 'Y-m-t' );
        $month_rev   = self::_sum_account_type( $module, 'revenue', $month_start, $month_end );
        $month_exp   = self::_sum_account_type( $module, 'expense', $month_start, $month_end );

        $gl    = self::gl_ids( $module );
        $cash  = 0.0;
        $balances = self::_account_balances( $module, null, null, true );
        foreach ( $balances as $b ) {
            if ( in_array( (int) $b['id'], array_values( $gl ), true ) || in_array( $b['account_type'] ?? '', [ 'asset', 'bank' ], true ) ) {
                $cash += (float) $b['total_debit'] - (float) $b['total_credit'];
            }
        }

        return [
            'meta'  => $meta,
            'stats' => [
                'pending'       => $pending,
                'posted'        => $posted,
                'confirmed'     => $confirmed,
                'month_revenue' => $month_rev,
                'month_expense' => $month_exp,
                'month_profit'  => $month_rev - $month_exp,
                'cash_position' => $cash,
            ],
        ];
    }

    private static function _sum_account_type( string $module, string $type, ?string $from, ?string $to ): float {
        $rows = self::_account_balances( $module, $from, $to, true );
        $sum  = 0.0;
        foreach ( $rows as $r ) {
            if ( ( $r['account_type'] ?? '' ) !== $type ) {
                continue;
            }
            $dr = (float) $r['total_debit'];
            $cr = (float) $r['total_credit'];
            $sum += $type === 'revenue' ? ( $cr - $dr ) : ( $dr - $cr );
        }
        return $sum;
    }

    public static function list_documents( string $module, int $limit = 80, string $state = '' ): array {
        global $wpdb;
        $pre   = $wpdb->prefix;
        $where = self::event_where( $module, 'e' );
        $args  = [];
        if ( $state ) {
            $where .= ' AND e.workflow_state = %s';
            $args[] = sanitize_text_field( $state );
        }
        $sql = "SELECT e.id, e.event_type, e.amount, e.workflow_state, e.reference_type, e.reference_id,
                       e.created_at, e.description";
        if ( self::has_col( 'ai_acc_events', 'source_module' ) ) {
            $sql .= ', e.source_module';
        } else {
            $sql .= ", '{$module}' AS source_module";
        }
        $sql .= " FROM {$pre}ai_acc_events e WHERE {$where} ORDER BY e.id DESC LIMIT %d";
        $args[] = $limit;

        $rows = $wpdb->get_results( $wpdb->prepare( $sql, ...$args ), ARRAY_A ) ?: [];
        foreach ( $rows as &$r ) {
            $r['ledger_tag'] = self::meta( $module )['ledger_tag'];
            self::_enrich_document( $module, $r );
        }
        return $rows;
    }

    private static function _enrich_document( string $module, array &$r ): void {
        global $wpdb;
        $pre = $wpdb->prefix;
        $ref_id = (int) ( $r['reference_id'] ?? 0 );

        if ( $module === 'arag_shop' && ( $r['reference_type'] ?? '' ) === 'arag_order' && $ref_id ) {
            $o = $wpdb->get_row( $wpdb->prepare(
                "SELECT order_number, billing_name FROM {$pre}arag_orders WHERE id = %d", $ref_id
            ), ARRAY_A );
            if ( $o ) {
                $r['order_number']  = $o['order_number'];
                $r['billing_name']  = $o['billing_name'];
            }
        }
        if ( $module === 'election' && ( $r['reference_type'] ?? '' ) === 'election_transaction' && $ref_id ) {
            $t = $wpdb->get_row( $wpdb->prepare(
                "SELECT direction, category, description, entry_date FROM {$pre}election_transactions WHERE id = %d", $ref_id
            ), ARRAY_A );
            if ( $t ) {
                $r['tx_direction'] = $t['direction'];
                $r['tx_category']  = $t['category'];
                $r['tx_label']     = $t['description'];
                $r['entry_date']   = $t['entry_date'];
            }
        }
        if ( $module === 'service_portal' && $ref_id && ( $r['reference_type'] ?? '' ) === 'svc_report' ) {
            $s = $wpdb->get_row( $wpdb->prepare(
                "SELECT report_no, customer_name, total FROM {$pre}svc_reports WHERE id = %d", $ref_id
            ), ARRAY_A );
            if ( $s ) {
                $r['report_no']      = $s['report_no'];
                $r['customer_name']  = $s['customer_name'];
            }
        }
    }

    public static function event_detail( string $module, int $id ): ?array {
        global $wpdb;
        $pre   = $wpdb->prefix;
        $where = self::event_where( $module, 'e' );
        $row   = $wpdb->get_row( $wpdb->prepare(
            "SELECT e.* FROM {$pre}ai_acc_events e WHERE e.id = %d AND {$where}", $id
        ), ARRAY_A );
        if ( ! $row ) {
            return null;
        }
        $flows = $wpdb->get_results( $wpdb->prepare(
            "SELECT f.id AS flow_id, f.from_account_id, f.to_account_id, f.amount, f.description, f.is_confirmed,
                    fa.account_code AS from_code, fa.account_name AS from_name,
                    ta.account_code AS to_code, ta.account_name AS to_name
             FROM {$pre}ai_acc_flows f
             LEFT JOIN {$pre}ai_acc_accounts fa ON fa.account_id = f.from_account_id
             LEFT JOIN {$pre}ai_acc_accounts ta ON ta.account_id = f.to_account_id
             WHERE f.event_id = %d ORDER BY f.line_no, f.id",
            $id
        ), ARRAY_A ) ?: [];
        $row['flows'] = $flows;
        self::_enrich_document( $module, $row );
        return $row;
    }

    public static function ledger( string $module, ?string $from, ?string $to, int $limit = 200 ): array {
        global $wpdb;
        $pre   = $wpdb->prefix;
        $where = self::event_where( $module, 'e' );
        $conds = [ $where, 'f.is_confirmed = 1' ];
        $args  = [];
        if ( $from ) {
            $conds[] = 'f.created_at >= %s';
            $args[]  = sanitize_text_field( $from ) . ' 00:00:00';
        }
        if ( $to ) {
            $conds[] = 'f.created_at <= %s';
            $args[]  = sanitize_text_field( $to ) . ' 23:59:59';
        }
        $fwhere = implode( ' AND ', $conds );
        $args[] = $limit;

        $sql = "SELECT f.id, f.event_id, f.amount, f.description, f.created_at,
                       fa.account_code AS dr_code, fa.account_name AS dr_name,
                       ta.account_code AS cr_code, ta.account_name AS cr_name,
                       e.reference_type, e.reference_id, e.event_type
                FROM {$pre}ai_acc_flows f
                INNER JOIN {$pre}ai_acc_events e ON e.id = f.event_id AND {$fwhere}
                LEFT JOIN {$pre}ai_acc_accounts fa ON fa.account_id = f.from_account_id
                LEFT JOIN {$pre}ai_acc_accounts ta ON ta.account_id = f.to_account_id
                ORDER BY f.created_at DESC, f.id DESC LIMIT %d";

        $rows = $wpdb->get_results( $wpdb->prepare( $sql, ...$args ), ARRAY_A ) ?: [];
        foreach ( $rows as &$r ) {
            $doc = [ 'reference_type' => $r['reference_type'], 'reference_id' => $r['reference_id'] ];
            self::_enrich_document( $module, $doc );
            $r = array_merge( $r, $doc );
        }
        return $rows;
    }

    public static function trial_balance( string $module, ?string $from, ?string $to ): array {
        $rows = self::_account_balances( $module, $from, $to, true );
        $total_dr = 0.0;
        $total_cr = 0.0;
        foreach ( $rows as &$r ) {
            $r['net_balance'] = (float) $r['total_debit'] - (float) $r['total_credit'];
            $total_dr += (float) $r['total_debit'];
            $total_cr += (float) $r['total_credit'];
        }
        return [
            'rows'     => $rows,
            'total_dr' => $total_dr,
            'total_cr' => $total_cr,
            'balanced' => abs( $total_dr - $total_cr ) < 0.01,
            'meta'     => self::meta( $module ),
        ];
    }

    public static function income_statement( string $module, ?string $from, ?string $to ): array {
        $rows = self::_account_balances( $module, $from, $to, true );
        $revenue = [];
        $contra  = [];
        $expense = [];
        $rev_t = $con_t = $exp_t = 0.0;

        foreach ( $rows as $r ) {
            $dr = (float) $r['total_debit'];
            $cr = (float) $r['total_credit'];
            $line = [ 'code' => $r['code'], 'name' => $r['name'] ];
            switch ( $r['account_type'] ) {
                case 'revenue':
                    $line['amount'] = $cr - $dr;
                    $revenue[] = $line;
                    $rev_t += $line['amount'];
                    break;
                case 'contra':
                    $line['amount'] = $dr - $cr;
                    $contra[] = $line;
                    $con_t += $line['amount'];
                    break;
                case 'expense':
                    $line['amount'] = $dr - $cr;
                    $expense[] = $line;
                    $exp_t += $line['amount'];
                    break;
            }
        }

        return [
            'revenue'    => $revenue,
            'contra'     => $contra,
            'expense_gl' => $expense,
            'expense_op' => [],
            'totals'     => [
                'revenue'       => $rev_t,
                'contra'        => $con_t,
                'expense_gl'    => $exp_t,
                'expense_op'    => 0,
                'expense_total' => $exp_t,
                'net_income'    => $rev_t - $con_t - $exp_t,
            ],
            'meta'   => self::meta( $module ),
            'period' => [ 'from' => $from, 'to' => $to ],
        ];
    }

    /** صدور سند GL برای تراکنش انتخابات */
    public static function post_election_transaction( int $tx_id ): array {
        global $wpdb;
        $pre = $wpdb->prefix;
        $tt  = $pre . 'election_transactions';
        if ( $wpdb->get_var( "SHOW TABLES LIKE '{$tt}'" ) !== $tt ) {
            return [ 'success' => false, 'message' => 'جدول تراکنش وجود ندارد' ];
        }
        $tx = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$tt} WHERE id = %d", $tx_id ), ARRAY_A );
        if ( ! $tx ) {
            return [ 'success' => false, 'message' => 'تراکنش یافت نشد' ];
        }
        if ( ! empty( $tx['acc_event_id'] ) ) {
            return [ 'success' => true, 'event_id' => (int) $tx['acc_event_id'], 'already' => true ];
        }

        self::ensure_chart( 'election' );
        $gl  = self::gl_ids( 'election' );
        $portal = self::portal_settings( 'election' );
        $tag = '[' . ( $portal['ledger_tag'] ?? 'ELECTION' ) . '|' . ( $portal['office_name'] ?? 'ستاد' ) . ']';
        $amount = (float) $tx['amount'];
        $cat = sanitize_key( $tx['category'] ?? 'other' );
        $exp_map = [
            'media'        => $gl['expense_media'] ?? 3151,
            'distribution' => $gl['expense_media'] ?? 3151,
            'goods'        => $gl['expense_goods'] ?? 3153,
            'field'        => $gl['expense_field'] ?? 3152,
            'staff'        => $gl['expense_staff'] ?? 3154,
            'logistics'    => $gl['expense_logistics'] ?? 3155,
            'tech'         => $gl['expense_tech'] ?? 3156,
        ];
        $exp_gl = $exp_map[ $cat ] ?? ( $gl['expense_other'] ?? 3159 );
        $bank   = $gl['bank'] ?? 3113;
        $rev_gl = $gl['revenue_income'] ?? 3141;

        $is_in = ( $tx['direction'] ?? '' ) === 'in';
        $pairs = $is_in
            ? [ [ 'dr_gl_id' => $bank, 'cr_gl_id' => $rev_gl, 'source_field' => 'amount', 'desc' => $tag . ' دریافت کمپین' ] ]
            : [ [ 'dr_gl_id' => $exp_gl, 'cr_gl_id' => $bank, 'source_field' => 'amount', 'desc' => $tag . ' هزینه کمپین — ' . $cat ] ];

        require_once ACC_PATH . 'includes/helpers/acc-event-engine.php';
        $etype = $is_in ? 'ELECTION_RECEIPT' : 'ELECTION_EXPENSE';
        $result = Acc_Event_Engine::emit_and_post( [
            'event_type'      => $etype,
            'amount'          => $amount,
            'payload'         => [
                'amount'         => $amount,
                'category'       => $cat,
                'description'    => $tx['description'] ?? '',
                'source_module'  => 'election',
                'manual_entries' => [ 'pairs' => $pairs ],
            ],
            'reference_type'  => 'election_transaction',
            'reference_id'    => $tx_id,
            'idempotency_key' => 'ELC_TX_' . $tx_id,
            'source_module'   => 'election',
            'description'     => $tag . ' ' . ( $tx['description'] ?? '' ),
        ] );

        if ( empty( $result['success'] ) ) {
            return $result;
        }
        $event_id = (int) ( $result['event_id'] ?? 0 );
        if ( $event_id && self::has_col( 'election_transactions', 'acc_event_id' ) ) {
            $wpdb->update( $tt, [ 'acc_event_id' => $event_id ], [ 'id' => $tx_id ] );
        }
        if ( $event_id ) {
            self::confirm_event( 'election', $event_id );
        }
        return [ 'success' => true, 'event_id' => $event_id ];
    }

    public static function confirm_event( string $module, int $id ): array {
        global $wpdb;
        $pre = $wpdb->prefix;
        $where = self::event_where( $module, 'e' );
        $state = (string) $wpdb->get_var( $wpdb->prepare(
            "SELECT workflow_state FROM {$pre}ai_acc_events e WHERE e.id = %d AND {$where}", $id
        ) );
        if ( $state === '' ) {
            return [ 'success' => false, 'message' => 'رویداد یافت نشد', 'http' => 404 ];
        }
        if ( $state === 'confirmed' ) {
            return [ 'success' => true, 'already' => true ];
        }
        if ( $state !== 'posted' ) {
            return [ 'success' => false, 'message' => 'فقط سند پست‌شده قابل تأیید است', 'http' => 400 ];
        }
        $wpdb->query( $wpdb->prepare( "UPDATE {$pre}ai_acc_flows SET is_confirmed = 1 WHERE event_id = %d", $id ) );
        $upd = [ 'workflow_state' => 'confirmed' ];
        if ( self::has_col( 'ai_acc_events', 'status' ) ) {
            $upd['status'] = 'POSTED';
        }
        $wpdb->update( $pre . 'ai_acc_events', $upd, [ 'id' => $id ] );
        return [ 'success' => true, 'event_id' => $id ];
    }
}
