<?php
namespace App\Controllers;

use App\Core\Audit;
use App\Core\Auth;
use App\Core\Controller;
use App\Core\Database;
use App\Core\Dates;
use App\Core\Flash;
use App\Core\Num;
use App\Core\Request;
use App\Core\Response;
use App\Core\Settings;
use App\Services\ExportService;

/** Modul data master generik (18 entitas). */
class MasterController extends Controller
{
    protected string $module = 'master';

    /**
     * Definisi entitas master.
     * Setiap field: [key => ['label','type','required','options','width','help']]
     */
    public static function entities(): array
    {
        return [
            'area' => [
                'label' => 'Area', 'table' => 'areas', 'icon' => 'geo-alt',
                'deskripsi' => 'Wilayah operasional perusahaan.',
                'fields' => [
                    'code' => ['label' => 'Kode', 'type' => 'text', 'required' => true, 'unique' => true],
                    'name' => ['label' => 'Nama Area', 'type' => 'text', 'required' => true],
                    'region' => ['label' => 'Region', 'type' => 'text'],
                    'payment_term_days' => ['label' => 'Termin Default (hari)', 'type' => 'int', 'help' => 'Kosongkan untuk mengikuti pengaturan global.'],
                    'is_active' => ['label' => 'Aktif', 'type' => 'bool'],
                ],
                'list' => ['code', 'name', 'region', 'payment_term_days', 'is_active'],
            ],
            'unit-bisnis' => [
                'label' => 'Unit Bisnis', 'table' => 'business_units', 'icon' => 'building',
                'deskripsi' => 'Segmentasi bisnis (Komersial / Residensial).',
                'fields' => [
                    'code' => ['label' => 'Kode', 'type' => 'text', 'required' => true, 'unique' => true],
                    'name' => ['label' => 'Nama Unit Bisnis', 'type' => 'text', 'required' => true],
                    'description' => ['label' => 'Keterangan', 'type' => 'textarea'],
                    'payment_term_days' => ['label' => 'Termin Default (hari)', 'type' => 'int'],
                    'is_active' => ['label' => 'Aktif', 'type' => 'bool'],
                ],
                'list' => ['code', 'name', 'description', 'payment_term_days', 'is_active'],
            ],
            'customer' => [
                'label' => 'Customer', 'table' => 'customers', 'icon' => 'people',
                'deskripsi' => 'Data pelanggan beserta termin, limit kredit, dan kategori risiko.',
                'fields' => [
                    'code' => ['label' => 'Kode Customer', 'type' => 'text', 'required' => true, 'unique' => true],
                    'name' => ['label' => 'Nama Customer', 'type' => 'text', 'required' => true],
                    'npwp' => ['label' => 'NPWP', 'type' => 'text'],
                    'nitku' => ['label' => 'NITKU', 'type' => 'text'],
                    'billing_address' => ['label' => 'Alamat Penagihan', 'type' => 'textarea'],
                    'tax_address' => ['label' => 'Alamat Pajak', 'type' => 'textarea'],
                    'contact_person' => ['label' => 'Narahubung', 'type' => 'text'],
                    'phone' => ['label' => 'Telepon', 'type' => 'text'],
                    'email' => ['label' => 'Email', 'type' => 'email'],
                    'payment_term_days' => ['label' => 'Termin Pembayaran (hari)', 'type' => 'int'],
                    'credit_limit' => ['label' => 'Limit Kredit', 'type' => 'money'],
                    'area_id' => ['label' => 'Area', 'type' => 'ref', 'ref' => 'areas'],
                    'business_unit_id' => ['label' => 'Unit Bisnis', 'type' => 'ref', 'ref' => 'business_units'],
                    'salesperson_id' => ['label' => 'Sales', 'type' => 'ref', 'ref' => 'salespersons'],
                    'collection_officer_id' => ['label' => 'Petugas Penagihan', 'type' => 'ref', 'ref' => 'collection_officers'],
                    'risk_category' => ['label' => 'Kategori Risiko', 'type' => 'enum',
                        'options' => ['RENDAH' => 'Rendah', 'SEDANG' => 'Sedang', 'TINGGI' => 'Tinggi', 'SANGAT_TINGGI' => 'Sangat Tinggi']],
                    'status' => ['label' => 'Status', 'type' => 'enum',
                        'options' => ['AKTIF' => 'Aktif', 'TIDAK_AKTIF' => 'Tidak Aktif', 'BLOKIR' => 'Blokir']],
                    'notes' => ['label' => 'Catatan', 'type' => 'textarea'],
                ],
                'list' => ['code', 'name', 'npwp', 'contact_person', 'phone', 'payment_term_days', 'credit_limit', 'risk_category', 'status'],
                'search' => ['code', 'name', 'npwp', 'contact_person', 'email'],
            ],
            'outlet' => [
                'label' => 'Outlet', 'table' => 'outlets', 'icon' => 'shop',
                'deskripsi' => 'Cabang/gerai milik customer.',
                'fields' => [
                    'code' => ['label' => 'Kode Outlet', 'type' => 'text', 'required' => true, 'unique' => true],
                    'name' => ['label' => 'Nama Outlet', 'type' => 'text', 'required' => true],
                    'customer_id' => ['label' => 'Customer', 'type' => 'ref', 'ref' => 'customers', 'required' => true],
                    'address' => ['label' => 'Alamat', 'type' => 'textarea'],
                    'area_id' => ['label' => 'Area', 'type' => 'ref', 'ref' => 'areas'],
                    'contact_person' => ['label' => 'Narahubung', 'type' => 'text'],
                    'phone' => ['label' => 'Telepon', 'type' => 'text'],
                    'payment_term_days' => ['label' => 'Termin Khusus (hari)', 'type' => 'int'],
                    'is_active' => ['label' => 'Aktif', 'type' => 'bool'],
                ],
                'list' => ['code', 'name', 'customer_id', 'area_id', 'contact_person', 'payment_term_days', 'is_active'],
                'search' => ['code', 'name'],
            ],
            'sales' => [
                'label' => 'Sales', 'table' => 'salespersons', 'icon' => 'person-badge',
                'deskripsi' => 'Tenaga penjualan, CSR, dan kanal penjualan.',
                'fields' => [
                    'code' => ['label' => 'Kode', 'type' => 'text', 'required' => true, 'unique' => true],
                    'name' => ['label' => 'Nama', 'type' => 'text', 'required' => true],
                    'type' => ['label' => 'Tipe', 'type' => 'enum', 'options' => ['INDIVIDU' => 'Individu', 'KANAL' => 'Kanal', 'UNIT' => 'Unit']],
                    'area_id' => ['label' => 'Area', 'type' => 'ref', 'ref' => 'areas'],
                    'email' => ['label' => 'Email', 'type' => 'email'],
                    'phone' => ['label' => 'Telepon', 'type' => 'text'],
                    'is_active' => ['label' => 'Aktif', 'type' => 'bool'],
                ],
                'list' => ['code', 'name', 'type', 'area_id', 'email', 'phone', 'is_active'],
            ],
            'petugas-penagihan' => [
                'label' => 'Petugas Penagihan', 'table' => 'collection_officers', 'icon' => 'person-check',
                'deskripsi' => 'Petugas yang bertanggung jawab menagih piutang.',
                'fields' => [
                    'code' => ['label' => 'Kode', 'type' => 'text', 'required' => true, 'unique' => true],
                    'name' => ['label' => 'Nama', 'type' => 'text', 'required' => true],
                    'area_id' => ['label' => 'Area', 'type' => 'ref', 'ref' => 'areas'],
                    'email' => ['label' => 'Email', 'type' => 'email'],
                    'phone' => ['label' => 'Telepon', 'type' => 'text'],
                    'is_active' => ['label' => 'Aktif', 'type' => 'bool'],
                ],
                'list' => ['code', 'name', 'area_id', 'email', 'phone', 'is_active'],
            ],
            'kategori-pekerjaan' => [
                'label' => 'Kategori Pekerjaan', 'table' => 'job_categories', 'icon' => 'tools',
                'deskripsi' => 'Kelompok besar jenis pekerjaan (kolom JOBS di Excel).',
                'fields' => [
                    'code' => ['label' => 'Kode', 'type' => 'text', 'required' => true, 'unique' => true],
                    'name' => ['label' => 'Nama Kategori', 'type' => 'text', 'required' => true],
                    'is_active' => ['label' => 'Aktif', 'type' => 'bool'],
                ],
                'list' => ['code', 'name', 'is_active'],
            ],
            'jenis-pekerjaan' => [
                'label' => 'Jenis Pekerjaan', 'table' => 'job_types', 'icon' => 'wrench',
                'deskripsi' => 'Rincian pekerjaan (kolom STATUS di Excel) — sudah dinormalisasi dari 68 varian penulisan.',
                'fields' => [
                    'code' => ['label' => 'Kode', 'type' => 'text', 'required' => true, 'unique' => true],
                    'name' => ['label' => 'Nama Jenis Pekerjaan', 'type' => 'text', 'required' => true],
                    'job_category_id' => ['label' => 'Kategori Pekerjaan', 'type' => 'ref', 'ref' => 'job_categories'],
                    'is_active' => ['label' => 'Aktif', 'type' => 'bool'],
                ],
                'list' => ['code', 'name', 'job_category_id', 'is_active'],
            ],
            'kategori-pendapatan' => [
                'label' => 'Kategori Pendapatan', 'table' => 'revenue_categories', 'icon' => 'diagram-3',
                'deskripsi' => 'PMSC, By Case, Residensial, Online, Royalty, dsb.',
                'fields' => [
                    'code' => ['label' => 'Kode', 'type' => 'text', 'required' => true, 'unique' => true],
                    'name' => ['label' => 'Nama Kategori', 'type' => 'text', 'required' => true],
                    'payment_term_days' => ['label' => 'Termin Default (hari)', 'type' => 'int'],
                    'is_active' => ['label' => 'Aktif', 'type' => 'bool'],
                ],
                'list' => ['code', 'name', 'payment_term_days', 'is_active'],
            ],
            'termin' => [
                'label' => 'Termin Pembayaran', 'table' => 'payment_terms', 'icon' => 'calendar-range',
                'deskripsi' => 'Jangka waktu pembayaran dan tanggal dasar perhitungan jatuh tempo.',
                'fields' => [
                    'code' => ['label' => 'Kode', 'type' => 'text', 'required' => true, 'unique' => true],
                    'name' => ['label' => 'Nama Termin', 'type' => 'text', 'required' => true],
                    'days' => ['label' => 'Jumlah Hari', 'type' => 'int', 'required' => true],
                    'base_date_type' => ['label' => 'Tanggal Dasar', 'type' => 'enum', 'options' => [
                        'invoice_date' => 'Tanggal Invoice', 'sent_date' => 'Tanggal Kirim',
                        'received_date' => 'Tanggal Terima', 'tax_invoice_date' => 'Tanggal Faktur Pajak',
                        'wr_date' => 'Tanggal Work Request', 'manual' => 'Manual']],
                    'is_default' => ['label' => 'Termin Default', 'type' => 'bool'],
                    'is_active' => ['label' => 'Aktif', 'type' => 'bool'],
                ],
                'list' => ['code', 'name', 'days', 'base_date_type', 'is_default', 'is_active'],
            ],
            'bank' => [
                'label' => 'Bank', 'table' => 'banks', 'icon' => 'bank',
                'deskripsi' => 'Rekening penerimaan pembayaran.',
                'fields' => [
                    'code' => ['label' => 'Kode', 'type' => 'text', 'required' => true, 'unique' => true],
                    'name' => ['label' => 'Nama Bank', 'type' => 'text', 'required' => true],
                    'account_no' => ['label' => 'Nomor Rekening', 'type' => 'text'],
                    'account_name' => ['label' => 'Nama Pemilik Rekening', 'type' => 'text'],
                    'branch' => ['label' => 'Cabang', 'type' => 'text'],
                    'is_active' => ['label' => 'Aktif', 'type' => 'bool'],
                ],
                'list' => ['code', 'name', 'account_no', 'account_name', 'branch', 'is_active'],
            ],
            'jenis-potongan' => [
                'label' => 'Jenis Potongan', 'table' => 'deduction_types', 'icon' => 'scissors',
                'deskripsi' => 'PPh 23, potongan administrasi, dan potongan lainnya beserta cara hitungnya.',
                'fields' => [
                    'code' => ['label' => 'Kode', 'type' => 'text', 'required' => true, 'unique' => true],
                    'name' => ['label' => 'Nama Potongan', 'type' => 'text', 'required' => true],
                    'calc_type' => ['label' => 'Cara Hitung', 'type' => 'enum', 'options' => [
                        'percent_of_dpp' => 'Persentase dari DPP', 'percent_of_gross' => 'Persentase dari Nominal Bruto',
                        'fixed' => 'Nilai Tetap', 'manual' => 'Diisi Manual']],
                    'rate' => ['label' => 'Tarif / Nilai', 'type' => 'decimal'],
                    'target_column' => ['label' => 'Kolom Tujuan', 'type' => 'enum', 'options' => [
                        'pph23' => 'PPh 23', 'admin' => 'Administrasi', 'other' => 'Lain-lain', 'credit_note' => 'Nota Kredit']],
                    'reduces_revenue' => ['label' => 'Mengurangi Revenue', 'type' => 'bool'],
                    'sort_order' => ['label' => 'Urutan', 'type' => 'int'],
                    'is_active' => ['label' => 'Aktif', 'type' => 'bool'],
                ],
                'list' => ['code', 'name', 'calc_type', 'rate', 'target_column', 'is_active'],
            ],
            'jenis-revisi' => [
                'label' => 'Jenis Revisi (RF)', 'table' => 'revision_types', 'icon' => 'arrow-repeat',
                'deskripsi' => 'Klasifikasi alasan revisi invoice dan tindakan bawaannya.',
                'fields' => [
                    'code' => ['label' => 'Kode', 'type' => 'text', 'required' => true, 'unique' => true],
                    'name' => ['label' => 'Nama Jenis Revisi', 'type' => 'text', 'required' => true],
                    'default_action' => ['label' => 'Tindakan Bawaan', 'type' => 'enum', 'options' => [
                        'replaced' => 'Diganti Invoice Baru', 'cancelled' => 'Dibatalkan',
                        'revised' => 'Direvisi', 'written_off' => 'Dihapuskan']],
                    'requires_new_invoice' => ['label' => 'Wajib Invoice Pengganti', 'type' => 'bool'],
                    'keywords' => ['label' => 'Kata Kunci Deteksi Otomatis', 'type' => 'text', 'help' => 'Pisahkan dengan tanda | (pipa). Dipakai untuk mengklasifikasi keterangan RF saat import.'],
                    'sort_order' => ['label' => 'Urutan', 'type' => 'int'],
                    'is_active' => ['label' => 'Aktif', 'type' => 'bool'],
                ],
                'list' => ['code', 'name', 'default_action', 'requires_new_invoice', 'is_active'],
            ],
            'status-penagihan' => [
                'label' => 'Status Penagihan', 'table' => 'collection_statuses', 'icon' => 'list-check',
                'deskripsi' => 'Tahapan proses penagihan.',
                'fields' => [
                    'code' => ['label' => 'Kode', 'type' => 'text', 'required' => true, 'unique' => true],
                    'name' => ['label' => 'Nama Status', 'type' => 'text', 'required' => true],
                    'color' => ['label' => 'Warna', 'type' => 'enum', 'options' => [
                        'primary' => 'Biru', 'success' => 'Hijau', 'warning' => 'Kuning', 'danger' => 'Merah',
                        'info' => 'Biru Muda', 'secondary' => 'Abu-abu', 'dark' => 'Gelap']],
                    'is_final' => ['label' => 'Status Akhir', 'type' => 'bool'],
                    'is_dispute' => ['label' => 'Termasuk Sengketa', 'type' => 'bool'],
                    'sort_order' => ['label' => 'Urutan', 'type' => 'int'],
                    'is_active' => ['label' => 'Aktif', 'type' => 'bool'],
                ],
                'list' => ['code', 'name', 'color', 'is_final', 'is_dispute', 'sort_order', 'is_active'],
            ],
            'jenis-lampiran' => [
                'label' => 'Jenis Lampiran', 'table' => 'attachment_types', 'icon' => 'paperclip',
                'deskripsi' => 'Klasifikasi dokumen pendukung.',
                'fields' => [
                    'code' => ['label' => 'Kode', 'type' => 'text', 'required' => true, 'unique' => true],
                    'name' => ['label' => 'Nama Jenis Dokumen', 'type' => 'text', 'required' => true],
                    'sort_order' => ['label' => 'Urutan', 'type' => 'int'],
                    'is_active' => ['label' => 'Aktif', 'type' => 'bool'],
                ],
                'list' => ['code', 'name', 'sort_order', 'is_active'],
                'soft_delete' => false,
            ],
            'pemetaan-nilai' => [
                'label' => 'Pemetaan Nilai Import', 'table' => 'value_aliases', 'icon' => 'shuffle',
                'deskripsi' => 'Pemetaan nilai mentah Excel (mis. "RDN JKT", "RESINDESIAL") ke master data kanonik.',
                'fields' => [
                    'dimension' => ['label' => 'Dimensi', 'type' => 'enum', 'required' => true, 'options' => [
                        'area' => 'Area', 'job_category' => 'Kategori Pekerjaan', 'job_type' => 'Jenis Pekerjaan',
                        'revenue_category' => 'Kategori Pendapatan', 'salesperson' => 'Sales', 'send_channel' => 'Kanal Pengiriman']],
                    'raw_value' => ['label' => 'Nilai Mentah di Excel', 'type' => 'text', 'required' => true],
                    'target_id' => ['label' => 'ID Master Tujuan', 'type' => 'int'],
                    'secondary_target_id' => ['label' => 'ID Master Kedua (unit bisnis untuk area)', 'type' => 'int'],
                    'canonical_text' => ['label' => 'Teks Kanonik', 'type' => 'text'],
                ],
                'list' => ['dimension', 'raw_value', 'canonical_text', 'target_id', 'secondary_target_id'],
                'search' => ['raw_value', 'canonical_text'],
                'soft_delete' => false,
            ],
        ];
    }

    public function index(): void
    {
        $ents = self::entities();
        $db = Database::i();
        foreach ($ents as $k => &$e) {
            try {
                $soft = ($e['soft_delete'] ?? true) ? ' WHERE deleted_at IS NULL' : '';
                $e['jumlah'] = (int)$db->value('SELECT COUNT(*) FROM `' . $e['table'] . '`' . $soft);
            } catch (\Throwable $ex) { $e['jumlah'] = 0; }
        }
        unset($e);
        $this->view('master/index', [
            'judul' => 'Data Master',
            'subjudul' => count($ents) . ' modul data master',
            'entities' => $ents,
        ]);
    }

    private function entity(string $key): array
    {
        $ents = self::entities();
        if (!isset($ents[$key])) Response::abort(404, 'Modul data master tidak ditemukan.');
        return $ents[$key];
    }

    public function listing(array $params): void
    {
        $key = (string)($params['entity'] ?? '');
        $e = $this->entity($key);
        [$page, $per] = $this->pageParams();
        $cari = trim((string)Request::get('cari', ''));

        $soft = ($e['soft_delete'] ?? true);
        $w = $soft ? ['t.deleted_at IS NULL'] : ['1=1'];
        $p = [];
        if ($cari !== '') {
            $cols = $e['search'] ?? ['code', 'name'];
            $parts = [];
            foreach ($cols as $c) $parts[] = "t.`$c` LIKE :q";
            $w[] = '(' . implode(' OR ', $parts) . ')';
            $p['q'] = '%' . $cari . '%';
        }
        $where = implode(' AND ', $w);
        $orderCol = in_array('name', array_keys($e['fields']), true) ? 'name' : array_key_first($e['fields']);
        $sql = "SELECT t.* FROM `{$e['table']}` t WHERE $where ORDER BY t.`$orderCol`";
        $m = new \App\Models\Area();
        $res = $m->paginate($sql, $p, $page, $per, "SELECT COUNT(*) FROM `{$e['table']}` t WHERE $where");

        $this->view('master/listing', [
            'judul' => 'Data Master — ' . $e['label'],
            'subjudul' => $e['deskripsi'],
            'entity' => $e, 'key' => $key, 'p' => $res, 'cari' => $cari,
            'refs' => $this->refOptions($e),
        ]);
    }

    private function refOptions(array $e): array
    {
        $db = Database::i();
        $out = [];
        foreach ($e['fields'] as $k => $f) {
            if (($f['type'] ?? '') !== 'ref') continue;
            $t = $f['ref'];
            $limit = $t === 'customers' ? ' LIMIT 3000' : '';
            $rows = $db->all("SELECT id, name FROM `$t` WHERE deleted_at IS NULL ORDER BY name$limit");
            $map = [];
            foreach ($rows as $r) $map[(int)$r['id']] = $r['name'];
            $out[$k] = $map;
        }
        return $out;
    }

    public function create(array $params): void
    {
        $key = (string)($params['entity'] ?? '');
        $e = $this->entity($key);
        $this->view('master/form', [
            'judul' => 'Tambah ' . $e['label'], 'entity' => $e, 'key' => $key,
            'row' => null, 'refs' => $this->refOptions($e),
        ]);
    }

    public function edit(array $params): void
    {
        $key = (string)($params['entity'] ?? '');
        $e = $this->entity($key);
        $row = Database::i()->one("SELECT * FROM `{$e['table']}` WHERE id = :id", ['id' => (int)($params['id'] ?? 0)]);
        if (!$row) Response::abort(404, 'Data tidak ditemukan.');
        $this->view('master/form', [
            'judul' => 'Ubah ' . $e['label'], 'entity' => $e, 'key' => $key,
            'row' => $row, 'refs' => $this->refOptions($e),
        ]);
    }

    private function collect(array $e, ?array $existing = null): array
    {
        $data = [];
        foreach ($e['fields'] as $k => $f) {
            $type = $f['type'] ?? 'text';
            if ($type === 'bool') { $data[$k] = Request::bool($k) ? 1 : 0; continue; }
            $v = Request::post($k, null);
            if ($v === null || $v === '') {
                $data[$k] = in_array($type, ['int', 'decimal', 'money', 'ref'], true) ? null : null;
                if (!empty($f['required']) && $existing === null) $data[$k] = '';
                continue;
            }
            $data[$k] = match ($type) {
                'int', 'ref' => (int)Num::parse((string)$v),
                'decimal', 'money' => Num::parse((string)$v),
                default => mb_substr(trim((string)$v), 0, 2000),
            };
        }
        return $data;
    }

    public function store(array $params): void
    {
        $key = (string)($params['entity'] ?? '');
        $e = $this->entity($key);
        $data = $this->collect($e);
        foreach ($e['fields'] as $k => $f) {
            if (!empty($f['required']) && ($data[$k] === null || $data[$k] === '')) {
                Flash::error($f['label'] . ' wajib diisi.');
                $this->back('/master/' . $key . '/tambah');
            }
            if (!empty($f['unique']) && $data[$k] !== null) {
                if (Database::i()->value("SELECT COUNT(*) FROM `{$e['table']}` WHERE `$k` = ?", [$data[$k]])) {
                    Flash::error($f['label'] . ' "' . $data[$k] . '" sudah digunakan.');
                    $this->back('/master/' . $key . '/tambah');
                }
            }
        }
        $id = Database::i()->insert($e['table'], array_filter($data, fn($v) => $v !== null));
        Audit::log('master', 'create', $e['table'], $id, $e['label'] . ' baru ditambahkan', null, $data);
        Flash::success($e['label'] . ' berhasil ditambahkan.');
        $this->redirect('/master/' . $key);
    }

    public function update(array $params): void
    {
        $key = (string)($params['entity'] ?? '');
        $e = $this->entity($key);
        $id = (int)($params['id'] ?? 0);
        $before = Database::i()->one("SELECT * FROM `{$e['table']}` WHERE id = :id", ['id' => $id]);
        if (!$before) Response::abort(404, 'Data tidak ditemukan.');
        $data = $this->collect($e, $before);
        foreach ($e['fields'] as $k => $f) {
            if (!empty($f['required']) && ($data[$k] === null || $data[$k] === '')) {
                Flash::error($f['label'] . ' wajib diisi.');
                $this->back('/master/' . $key . '/' . $id . '/ubah');
            }
            if (!empty($f['unique']) && $data[$k] !== null) {
                if (Database::i()->value("SELECT COUNT(*) FROM `{$e['table']}` WHERE `$k` = ? AND id <> ?", [$data[$k], $id])) {
                    Flash::error($f['label'] . ' "' . $data[$k] . '" sudah digunakan.');
                    $this->back('/master/' . $key . '/' . $id . '/ubah');
                }
            }
        }
        Database::i()->update($e['table'], $data, 'id = :pk', ['pk' => $id]);
        Audit::logDiff('master', 'update', $e['table'], $id, $before, $data, $e['label'] . ' diubah');
        Flash::success($e['label'] . ' berhasil diperbarui.');
        $this->redirect('/master/' . $key);
    }

    public function destroy(array $params): void
    {
        $key = (string)($params['entity'] ?? '');
        $e = $this->entity($key);
        $id = (int)($params['id'] ?? 0);
        $db = Database::i();
        $row = $db->one("SELECT * FROM `{$e['table']}` WHERE id = :id", ['id' => $id]);
        if (!$row) Response::abort(404, 'Data tidak ditemukan.');

        // Cegah penghapusan yang masih dipakai transaksi
        $refs = [
            'customers' => ['invoices', 'customer_id'], 'outlets' => ['invoices', 'outlet_id'],
            'areas' => ['invoices', 'area_id'], 'business_units' => ['invoices', 'business_unit_id'],
            'salespersons' => ['invoices', 'salesperson_id'], 'collection_officers' => ['invoices', 'collection_officer_id'],
            'job_categories' => ['invoices', 'job_category_id'], 'job_types' => ['invoices', 'job_type_id'],
            'revenue_categories' => ['invoices', 'revenue_category_id'], 'banks' => ['payments', 'bank_id'],
            'deduction_types' => ['invoice_deductions', 'deduction_type_id'],
            'revision_types' => ['invoice_revisions', 'revision_type_id'],
            'collection_statuses' => ['collection_followups', 'collection_status_id'],
        ];
        if (isset($refs[$e['table']])) {
            [$t, $c] = $refs[$e['table']];
            $n = (int)$db->value("SELECT COUNT(*) FROM `$t` WHERE `$c` = ? AND deleted_at IS NULL", [$id]);
            if ($n > 0) {
                Flash::error($e['label'] . ' ini masih dipakai oleh ' . nf($n) . ' transaksi sehingga tidak dapat dihapus. '
                    . 'Nonaktifkan saja bila sudah tidak digunakan.');
                $this->back('/master/' . $key);
            }
        }
        if ($e['soft_delete'] ?? true) $db->exec("UPDATE `{$e['table']}` SET deleted_at = NOW() WHERE id = ?", [$id]);
        else $db->exec("DELETE FROM `{$e['table']}` WHERE id = ?", [$id]);

        Audit::log('master', 'delete', $e['table'], $id, $e['label'] . ' dihapus', $row, null);
        Flash::success($e['label'] . ' berhasil dihapus.');
        $this->redirect('/master/' . $key);
    }

    public function export(array $params): void
    {
        $key = (string)($params['entity'] ?? '');
        $e = $this->entity($key);
        $soft = ($e['soft_delete'] ?? true) ? ' WHERE deleted_at IS NULL' : '';
        $rows = Database::i()->all("SELECT * FROM `{$e['table']}`$soft ORDER BY id");
        $refs = $this->refOptions($e);
        $columns = [];
        foreach ($e['fields'] as $k => $f) {
            $type = match ($f['type'] ?? 'text') {
                'money' => 'money', 'int', 'ref' => 'int', 'decimal' => 'dec', 'bool' => 'text', default => 'text',
            };
            $columns[] = ['label' => $f['label'], 'key' => $k, 'type' => $type];
        }
        foreach ($rows as &$r) {
            foreach ($e['fields'] as $k => $f) {
                if (($f['type'] ?? '') === 'bool') $r[$k] = ((int)($r[$k] ?? 0) === 1) ? 'Ya' : 'Tidak';
                if (($f['type'] ?? '') === 'ref' && isset($refs[$k])) $r[$k] = $refs[$k][(int)($r[$k] ?? 0)] ?? '';
                if (($f['type'] ?? '') === 'enum') $r[$k] = $f['options'][$r[$k] ?? ''] ?? ($r[$k] ?? '');
            }
        }
        unset($r);
        ExportService::send((string)Request::get('format', 'xlsx'), [
            'judul' => 'DATA MASTER — ' . mb_strtoupper($e['label']),
            'subjudul' => $e['deskripsi'],
            'nama_berkas' => 'master_' . $key, 'sheet' => mb_substr($e['label'], 0, 31),
            'orientasi' => count($columns) > 6 ? 'landscape' : 'portrait',
        ], $columns, $rows);
    }
}
