<?php
namespace App\Services;

use App\Core\Database;
use App\Core\Settings;
use App\Models\Customer;

/**
 * Menerjemahkan nilai teks mentah dari Excel menjadi ID master data.
 *
 * Menyelesaikan DQ-06, DQ-17 s/d DQ-21:
 *  - "RDN JKT"           -> Area JAKARTA + Unit Bisnis RESIDENSIAL
 *  - "RESINDESIAL"       -> Kategori Pendapatan RESIDENSIAL
 *  - "INDUSTRIAL EQUIPEMNT" / "EWUIPMENT" / "INDUSRIAL" -> INDUSTRIAL EQUIPMENT
 *  - "DRAIN CLEANING -MAIN LINE" -> "DRAIN CLEANING - MAIN LINE"
 *  - nama customer/outlet dengan spasi ganda / spasi ujung -> satu entitas
 *  - "EMAIL TIA" -> kanal EMAIL, PIC TIA
 *
 * Pemetaan yang dipelajari disimpan di tabel `value_aliases` sehingga import
 * berikutnya konsisten dan administrator dapat mengoreksinya lewat menu master.
 */
class MasterResolver
{
    private array $cache = [];
    private array $aliasCache = [];
    public array $created = [];   // ringkasan master baru yang dibuat otomatis
    private bool $autoCreate;

    public function __construct(?bool $autoCreate = null)
    {
        $this->autoCreate = $autoCreate ?? Settings::bool('import.auto_buat_master', true);
    }

    private function db(): Database { return Database::i(); }

    // ------------------------------------------------------------------ util
    public static function norm(?string $v): string
    {
        if ($v === null) return '';
        $v = str_replace(["\xC2\xA0", "\t", "\r", "\n"], ' ', (string)$v);
        $v = preg_replace('/\s+/u', ' ', trim($v)) ?? '';
        return $v;
    }

    public static function key(?string $v): string
    {
        return mb_strtoupper(self::norm($v), 'UTF-8');
    }

    /** Kunci pencocokan longgar: hanya huruf & angka. */
    public static function fuzzyKey(?string $v): string
    {
        return preg_replace('/[^A-Z0-9]/', '', self::key($v)) ?? '';
    }

    private function alias(string $dimension, string $raw): ?array
    {
        $k = $dimension . '|' . $raw;
        if (array_key_exists($k, $this->aliasCache)) return $this->aliasCache[$k];
        $row = $this->db()->one(
            'SELECT * FROM value_aliases WHERE dimension = :d AND raw_value = :r LIMIT 1',
            ['d' => $dimension, 'r' => mb_substr($raw, 0, 190)]
        );
        return $this->aliasCache[$k] = $row;
    }

    private function saveAlias(string $dimension, string $raw, ?int $targetId, ?int $secondary = null, ?string $canonical = null): void
    {
        $raw = mb_substr($raw, 0, 190);
        $this->db()->exec(
            'INSERT INTO value_aliases (dimension, raw_value, target_id, secondary_target_id, canonical_text, is_auto)
             VALUES (:d, :r, :t, :s, :c, 1)
             ON DUPLICATE KEY UPDATE target_id = VALUES(target_id),
                 secondary_target_id = VALUES(secondary_target_id), canonical_text = VALUES(canonical_text)',
            ['d' => $dimension, 'r' => $raw, 't' => $targetId, 's' => $secondary, 'c' => $canonical]
        );
        unset($this->aliasCache[$dimension . '|' . $raw]);
    }

    private function noteCreated(string $type, string $name): void
    {
        $this->created[$type][] = $name;
    }

    /** Membuang cache pencarian sebuah tabel setelah ada baris baru. */
    private function forget(string $table): void
    {
        $prefix = $table . '|';
        foreach (array_keys($this->cache) as $k) {
            if (str_starts_with((string)$k, $prefix)) unset($this->cache[$k]);
        }
    }

    /**
     * Menyisipkan baris master dengan aman terhadap tabrakan kode unik.
     * Bila kode sudah dipakai (misalnya dua varian ejaan menghasilkan kode kanonik
     * yang sama), baris yang sudah ada dipakai kembali — bukan menimbulkan error.
     */
    private function insertMaster(string $table, array $data): ?array
    {
        $code = (string)($data['code'] ?? '');
        if ($code !== '') {
            $existing = $this->db()->one("SELECT * FROM `$table` WHERE code = :c LIMIT 1", ['c' => $code]);
            if ($existing) {
                if (!empty($existing['deleted_at'])) {
                    $this->db()->exec("UPDATE `$table` SET deleted_at = NULL WHERE id = ?", [(int)$existing['id']]);
                    $existing['deleted_at'] = null;
                }
                $this->forget($table);
                return $existing;
            }
        }
        try {
            $id = $this->db()->insert($table, $data);
        } catch (\PDOException $e) {
            if ((int)$e->errorInfo[1] !== 1062) throw $e;
            $this->forget($table);
            return $this->db()->one("SELECT * FROM `$table` WHERE code = :c LIMIT 1", ['c' => $code]);
        }
        $this->forget($table);
        return $this->db()->one("SELECT * FROM `$table` WHERE id = ?", [$id]);
    }

    /** Pencarian sederhana pada tabel master berdasarkan code atau name. */
    private function lookup(string $table, string $value): ?array
    {
        $ck = $table . '|' . $value;
        if (array_key_exists($ck, $this->cache)) return $this->cache[$ck];
        $row = $this->db()->one(
            "SELECT * FROM `$table` WHERE (UPPER(code) = :v OR UPPER(name) = :v2) AND deleted_at IS NULL LIMIT 1",
            ['v' => $value, 'v2' => $value]
        );
        if (!$row) {
            // Pencocokan longgar (abaikan tanda baca & spasi)
            $fz = self::fuzzyKey($value);
            if ($fz !== '') {
                $cands = $this->db()->all("SELECT * FROM `$table` WHERE deleted_at IS NULL");
                foreach ($cands as $c) {
                    if (self::fuzzyKey($c['name']) === $fz || self::fuzzyKey($c['code']) === $fz) { $row = $c; break; }
                }
            }
        }
        return $this->cache[$ck] = $row;
    }

    // ------------------------------------------------------------------ AREA + UNIT BISNIS
    /**
     * @return array{area_id:?int,business_unit_id:?int,catatan:?string}
     */
    public function area(?string $raw): array
    {
        $rawN = self::key($raw);
        if ($rawN === '') return ['area_id' => null, 'business_unit_id' => null, 'catatan' => null];

        if ($a = $this->alias('area', $rawN)) {
            return ['area_id' => $a['target_id'] ? (int)$a['target_id'] : null,
                    'business_unit_id' => $a['secondary_target_id'] ? (int)$a['secondary_target_id'] : null,
                    'catatan' => null];
        }

        // Deteksi prefiks residensial
        $isResidensial = (bool)preg_match('/^(RDN|RESIDENSIAL|RESINDESIAL|RES)\b|\bRDN\b/', $rawN);
        $areaPart = preg_replace('/^(RDN|RESIDENSIAL|RESINDESIAL|RES)\s*/', '', $rawN) ?? $rawN;
        $areaPart = self::key($areaPart);

        $areaMap = [
            'JKT' => 'JKT', 'JAKARTA' => 'JKT', 'JABODETABEK' => 'JKT', 'JAK' => 'JKT',
            'BALI' => 'BALI', 'DPS' => 'BALI', 'DENPASAR' => 'BALI',
            'BDG' => 'BDG', 'BANDUNG' => 'BDG', 'JABAR' => 'BDG',
        ];
        $code = $areaMap[$areaPart] ?? null;

        $areaRow = null;
        if ($code) $areaRow = $this->lookup('areas', $code);
        if (!$areaRow) $areaRow = $this->lookup('areas', $areaPart);

        $catatan = null;
        if (!$areaRow && $this->autoCreate && $areaPart !== '') {
            $newCode = mb_substr(preg_replace('/[^A-Z0-9]/', '', $areaPart) ?: 'AREA', 0, 20);
            $areaRow = $this->insertMaster('areas', ['code' => $newCode, 'name' => mb_convert_case($areaPart, MB_CASE_TITLE, 'UTF-8'), 'is_active' => 1]);
            $this->noteCreated('area', $areaPart);
            $catatan = 'Area baru dibuat otomatis: ' . $areaPart;
        }

        $buCode = $isResidensial ? 'RESIDENSIAL' : 'KOMERSIAL';
        $bu = $this->lookup('business_units', $buCode);

        $areaId = $areaRow ? (int)$areaRow['id'] : null;
        $buId   = $bu ? (int)$bu['id'] : null;
        $this->saveAlias('area', $rawN, $areaId, $buId, ($areaRow['name'] ?? '') . ($isResidensial ? ' / Residensial' : ' / Komersial'));
        return ['area_id' => $areaId, 'business_unit_id' => $buId, 'catatan' => $catatan];
    }

    // ------------------------------------------------------------------ KATEGORI PEKERJAAN
    public function jobCategory(?string $raw): ?int
    {
        $k = self::key($raw);
        if ($k === '') return null;
        if ($a = $this->alias('job_category', $k)) return $a['target_id'] ? (int)$a['target_id'] : null;

        $row = $this->lookup('job_categories', $k);
        if (!$row) {
            $syn = [
                'SEWERDRAIN' => 'SEWER_DRAIN', 'SEWER&DRAIN' => 'SEWER_DRAIN',
                'DRAINCLEANERPRODUCT' => 'DRAIN_CLEANER_PRODUCT', 'PRODUCT' => 'DRAIN_CLEANER_PRODUCT',
                'CLEANINGDUCTING' => 'CLEANING_DUCTING', 'DUCTING' => 'CLEANING_DUCTING',
                'CAMERA' => 'CAMERA', 'MOBILISASI' => 'MOBILISASI', 'PLUMBING' => 'PLUMBING',
                'PUMPING' => 'PUMPING', 'ROYALTY' => 'ROYALTY', 'ROYALTI' => 'ROYALTY',
                'SPAREPART' => 'SPARE_PART', 'LEAKDETECTOR' => 'LEAK_DETECTOR',
                'CLEANINGGROUNDWATERTANK' => 'CLEANING_GROUND_WATER_TANK',
                'OTHERS' => 'OTHERS', 'LAINLAIN' => 'OTHERS', 'LAINNYA' => 'OTHERS',
            ];
            $fz = self::fuzzyKey($k);
            if (isset($syn[$fz])) $row = $this->lookup('job_categories', $syn[$fz]);
        }
        if (!$row && $this->autoCreate) {
            $code = mb_substr(preg_replace('/[^A-Z0-9]+/', '_', $k) ?: 'KATEGORI', 0, 40);
            $row = $this->insertMaster('job_categories', ['code' => $code, 'name' => mb_convert_case($k, MB_CASE_TITLE, 'UTF-8'), 'is_active' => 1]);
            if ($row) $this->noteCreated('kategori_pekerjaan', $k);
        }
        $id = $row ? (int)$row['id'] : null;
        $this->saveAlias('job_category', $k, $id, null, $row['name'] ?? null);
        return $id;
    }

    // ------------------------------------------------------------------ JENIS PEKERJAAN (kolom STATUS)
    public function jobType(?string $raw, ?int $jobCategoryId = null): ?int
    {
        $k = self::key($raw);
        if ($k === '') return null;
        if ($a = $this->alias('job_type', $k)) return $a['target_id'] ? (int)$a['target_id'] : null;

        // Normalisasi varian penulisan (DQ-20)
        $canon = self::canonicalJobType($k);
        $row = $this->lookup('job_types', $canon);
        if (!$row) $row = $this->lookup('job_types', $k);
        if (!$row && $this->autoCreate) {
            $code = mb_substr(preg_replace('/[^A-Z0-9]+/', '_', $canon) ?: 'JENIS', 0, 60);
            $row = $this->insertMaster('job_types', [
                'code' => $code, 'name' => mb_convert_case($canon, MB_CASE_TITLE, 'UTF-8'),
                'job_category_id' => $jobCategoryId, 'is_active' => 1,
            ]);
            if ($row) $this->noteCreated('jenis_pekerjaan', $canon);
        }
        $id = $row ? (int)$row['id'] : null;
        $this->saveAlias('job_type', $k, $id, null, $row['name'] ?? $canon);
        return $id;
    }

    /** Menyatukan 68 varian penulisan jenis pekerjaan menjadi bentuk kanonik. */
    public static function canonicalJobType(string $k): string
    {
        $k = self::key($k);
        if ($k === '') return '';
        // Perbaikan salah ejaan
        $fix = [
            'KICTHEN' => 'KITCHEN', 'EQUIPEMENT' => 'EQUIPMENT', 'EQUIPEMNT' => 'EQUIPMENT',
            'EWUIPMENT' => 'EQUIPMENT', 'INDUSRIAL' => 'INDUSTRIAL', 'INDUSTRIAL EQUIPEMENT' => 'INDUSTRIAL EQUIPMENT',
            'LINELOC' => 'LINE LOCATOR', 'SPAREPART' => 'SPARE PART',
        ];
        foreach ($fix as $bad => $good) $k = str_replace($bad, $good, $k);

        // "DRAIN CLEANING -MAIN LINE" / "DRAIN CLEANING - MAIN LINE" -> seragam
        if (preg_match('/^DRAIN\s*CLEANING\s*[-–]?\s*(.*)$/u', $k, $m)) {
            $sub = self::norm($m[1]);
            if ($sub === '') return 'DRAIN CLEANING';
            $subMap = ['MAIN LINE' => 'MAIN LINE', 'MAINLINE' => 'MAIN LINE', 'KITCHEN' => 'KITCHEN',
                       'FLOOR' => 'FLOOR', 'TOILET' => 'TOILET', 'WASH' => 'WASH', 'LAUNDRY' => 'LAUNDRY',
                       'URINAL' => 'URINAL', 'OTHER' => 'OTHERS', 'OTHERS' => 'OTHERS'];
            $sub = $subMap[$sub] ?? $sub;
            return 'DRAIN CLEANING - ' . $sub;
        }
        if (preg_match('/^DUCTING\s*&\s*DRAIN\s*CLEANING/u', $k)) return 'DUCTING & DRAIN CLEANING';
        if (preg_match('/^DRAIN\s*CLEANING\s*&\s*DUCTING/u', $k)) return 'DUCTING & DRAIN CLEANING';
        if (str_contains($k, 'INDUSTRIAL') && str_contains($k, 'EQUIPMENT')) return 'INDUSTRIAL EQUIPMENT';
        if (str_contains($k, 'CLEANING GROUND WATER')) return 'CLEANING GROUND WATER TANK';
        if (str_contains($k, 'EXCAVATION') && str_contains($k, 'CAMERA')) return 'EXCAVATION CAMERA';
        if (str_contains($k, 'EXCAVATION') && str_contains($k, 'LINE LOCATOR')) return 'EXCAVATION - LINE LOCATOR';
        return $k;
    }

    // ------------------------------------------------------------------ KATEGORI PENDAPATAN (PMSC/BY CASE)
    public function revenueCategory(?string $raw): ?int
    {
        $k = self::key($raw);
        if ($k === '') {
            $row = $this->lookup('revenue_categories', 'TIDAK_DIKATEGORIKAN');
            return $row ? (int)$row['id'] : null;
        }
        if ($a = $this->alias('revenue_category', $k)) return $a['target_id'] ? (int)$a['target_id'] : null;

        $fz = self::fuzzyKey($k);
        $map = [
            'PMSC' => 'PMSC',
            'BYCASE' => 'BY_CASE', 'BYCASES' => 'BY_CASE',
            'RESIDENSIAL' => 'RESIDENSIAL', 'RESINDESIAL' => 'RESIDENSIAL', 'RESIDENSIL' => 'RESIDENSIAL',
            'RESIDENTIAL' => 'RESIDENSIAL', 'RDN' => 'RESIDENSIAL',
            'ONLINE' => 'ONLINE', 'PENJUALANONLINE' => 'ONLINE',
            'ROYALTY' => 'ROYALTY', 'ROYALTI' => 'ROYALTY',
            'HE' => 'HE', 'REFERRALFEE' => 'REFERRAL_FEE',
        ];
        $code = $map[$fz] ?? null;
        $row = $code ? $this->lookup('revenue_categories', $code) : $this->lookup('revenue_categories', $k);
        if (!$row && $this->autoCreate) {
            $newCode = mb_substr(preg_replace('/[^A-Z0-9]+/', '_', $k) ?: 'KATEGORI', 0, 40);
            $row = $this->insertMaster('revenue_categories', ['code' => $newCode, 'name' => mb_convert_case($k, MB_CASE_TITLE, 'UTF-8'), 'is_active' => 1]);
            if ($row) $this->noteCreated('kategori_pendapatan', $k);
        }
        $id = $row ? (int)$row['id'] : null;
        $this->saveAlias('revenue_category', $k, $id, null, $row['name'] ?? null);
        return $id;
    }

    // ------------------------------------------------------------------ SALES
    public function salesperson(?string $raw): ?int
    {
        $k = self::key($raw);
        if ($k === '') return null;
        if ($a = $this->alias('salesperson', $k)) return $a['target_id'] ? (int)$a['target_id'] : null;

        $row = $this->lookup('salespersons', $k);
        if (!$row && $this->autoCreate) {
            // CSR / OFFICE / nama area bukan individu
            $type = in_array($k, ['CSR', 'OFFICE', 'ADMIN', 'ONLINE'], true) ? 'KANAL'
                  : (in_array($k, ['BALI', 'JAKARTA', 'JKT', 'BANDUNG', 'BDG'], true) ? 'UNIT' : 'INDIVIDU');
            $code = mb_substr(preg_replace('/[^A-Z0-9]+/', '_', $k) ?: 'SALES', 0, 40);
            $areaId = null;
            if ($type === 'UNIT') {
                $ar = $this->area($k);
                $areaId = $ar['area_id'];
            }
            $row = $this->insertMaster('salespersons', [
                'code' => $code, 'name' => mb_convert_case($k, MB_CASE_TITLE, 'UTF-8'),
                'type' => $type, 'area_id' => $areaId, 'is_active' => 1,
            ]);
            if ($row) $this->noteCreated('sales', $k);
        }
        $id = $row ? (int)$row['id'] : null;
        $this->saveAlias('salesperson', $k, $id, null, $row['name'] ?? null);
        return $id;
    }

    // ------------------------------------------------------------------ CUSTOMER & OUTLET
    /**
     * @return array{customer_id:?int,baru:bool}
     */
    public function customer(?string $raw, array $ctx = []): array
    {
        $name = Customer::normalizeName((string)$raw);
        if ($name === '') return ['customer_id' => null, 'baru' => false];

        $ck = 'cust|' . $name;
        if (isset($this->cache[$ck])) return ['customer_id' => $this->cache[$ck], 'baru' => false];

        $m = new Customer();
        $row = $m->findByName($name);
        if (!$row) {
            // Pencocokan longgar untuk menyatukan varian spasi/tanda baca (DQ-17)
            $fz = self::fuzzyKey($name);
            $cand = $this->db()->one(
                "SELECT id, name FROM customers
                  WHERE deleted_at IS NULL
                    AND UPPER(REPLACE(REPLACE(REPLACE(REPLACE(name,' ',''),'.',''),'-',''),',','')) = :fz
                  LIMIT 1",
                ['fz' => $fz]
            );
            if ($cand) $row = $cand;
        }
        $baru = false;
        if (!$row) {
            if (!$this->autoCreate) return ['customer_id' => null, 'baru' => false];
            $data = [
                'code' => $m->generateCode($name),
                'name' => $name,
                'status' => 'AKTIF',
                'area_id' => $ctx['area_id'] ?? null,
                'business_unit_id' => $ctx['business_unit_id'] ?? null,
                'salesperson_id' => $ctx['salesperson_id'] ?? null,
                'credit_limit' => 0,
                'risk_category' => 'RENDAH',
            ];
            $id = $m->create($data);
            $row = ['id' => $id, 'name' => $name];
            $baru = true;
            $this->noteCreated('customer', $name);
        }
        $this->cache[$ck] = (int)$row['id'];
        return ['customer_id' => (int)$row['id'], 'baru' => $baru];
    }

    public function outlet(?string $raw, int $customerId, array $ctx = []): ?int
    {
        $name = Customer::normalizeName((string)$raw);
        if ($name === '' || $customerId <= 0) return null;

        $ck = 'outlet|' . $customerId . '|' . $name;
        if (isset($this->cache[$ck])) return $this->cache[$ck];

        $row = $this->db()->one(
            'SELECT id FROM outlets WHERE customer_id = :c AND name = :n AND deleted_at IS NULL LIMIT 1',
            ['c' => $customerId, 'n' => $name]
        );
        if (!$row) {
            $fz = self::fuzzyKey($name);
            $row = $this->db()->one(
                "SELECT id FROM outlets WHERE customer_id = :c AND deleted_at IS NULL
                   AND UPPER(REPLACE(REPLACE(REPLACE(REPLACE(name,' ',''),'.',''),'-',''),',','')) = :fz LIMIT 1",
                ['c' => $customerId, 'fz' => $fz]
            );
        }
        if (!$row) {
            if (!$this->autoCreate) return null;
            $base = 'O-' . mb_substr(preg_replace('/[^A-Z0-9]/', '', $name) ?: 'OUTLET', 0, 12);
            $code = $base; $i = 1;
            while ($this->db()->value('SELECT COUNT(*) FROM outlets WHERE code = ?', [$code]) > 0) {
                $code = $base . '-' . (++$i);
                if ($i > 9999) { $code = 'O-' . strtoupper(bin2hex(random_bytes(4))); break; }
            }
            $id = $this->db()->insert('outlets', [
                'customer_id' => $customerId, 'code' => $code, 'name' => $name,
                'area_id' => $ctx['area_id'] ?? null, 'is_active' => 1,
            ]);
            $row = ['id' => $id];
            $this->noteCreated('outlet', $name);
        }
        return $this->cache[$ck] = (int)$row['id'];
    }

    // ------------------------------------------------------------------ KANAL PENGIRIMAN
    /**
     * Memisahkan "EMAIL TIA" menjadi kanal EMAIL + PIC TIA.
     * @return array{channel:?string,pic:?string}
     */
    public function sendChannel(?string $raw): array
    {
        $k = self::key($raw);
        if ($k === '') return ['channel' => null, 'pic' => null];

        $rules = [
            '/^POS\b/'                 => 'POS',
            '/^EMAIL\b/'               => 'EMAIL',
            '/^E-?MAIL\b/'             => 'EMAIL',
            '/^WA\b|^WHATSAPP\b|^W\.A\b/' => 'WHATSAPP',
            '/^TUKAR\s*FAKTUR/'        => 'TUKAR FAKTUR',
            '/^PORTAL/'                => 'PORTAL VENDOR',
            '/^KURIR|^EKSPEDISI|^JNE|^JNT|^TIKI/' => 'KURIR',
            '/^AMBIL|^DIAMBIL|^PICKUP/'=> 'DIAMBIL CUSTOMER',
            '/^FAX/'                   => 'FAKS',
        ];
        foreach ($rules as $re => $channel) {
            if (preg_match($re, $k)) {
                $pic = self::norm(preg_replace($re, '', $k) ?? '');
                $pic = trim($pic, " -:/");
                return ['channel' => $channel, 'pic' => $pic !== '' ? mb_substr($pic, 0, 120) : null];
            }
        }
        return ['channel' => 'LAIN-LAIN', 'pic' => mb_substr($k, 0, 120)];
    }

    // ------------------------------------------------------------------ JENIS REVISI
    /** Mengklasifikasikan keterangan RF menjadi revision_type berdasarkan kata kunci. */
    public function revisionType(?string $reason, bool $hasNewInvoice): array
    {
        $txt = mb_strtolower(self::norm((string)$reason), 'UTF-8');
        $types = $this->db()->all('SELECT * FROM revision_types WHERE deleted_at IS NULL AND is_active = 1 ORDER BY sort_order');
        foreach ($types as $t) {
            foreach (array_filter(array_map('trim', explode('|', (string)$t['keywords']))) as $kw) {
                if ($kw !== '' && str_contains($txt, mb_strtolower($kw, 'UTF-8'))) {
                    $action = $t['default_action'];
                    // Bila tidak ada invoice pengganti, tindakan "replaced" tidak masuk akal
                    if ($action === 'replaced' && !$hasNewInvoice) $action = 'cancelled';
                    return ['revision_type_id' => (int)$t['id'], 'action' => $action, 'name' => $t['name']];
                }
            }
        }
        $lain = $this->lookup('revision_types', 'LAINNYA');
        return ['revision_type_id' => $lain ? (int)$lain['id'] : null,
                'action' => $hasNewInvoice ? 'replaced' : 'cancelled',
                'name' => $lain['name'] ?? 'Lain-lain'];
    }

    public function deductionTypeId(string $code): ?int
    {
        $row = $this->lookup('deduction_types', $code);
        return $row ? (int)$row['id'] : null;
    }

    public function summaryCreated(): array
    {
        $out = [];
        foreach ($this->created as $type => $names) {
            $out[$type] = ['jumlah' => count($names), 'contoh' => array_slice(array_unique($names), 0, 10)];
        }
        return $out;
    }
}
