import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
import {
  Search, ChevronRight, ChevronDown, FolderTree, RefreshCw, Loader2,
  Server, HardDrive, Layers, Network, GitBranch, Shield, ShieldCheck,
  Route, ArrowUpRight, Globe, Plug, MapPin, Scale, Database, FolderOpen,
  Link, Box, Boxes, Zap, Webhook, Bell, FileText, MessageSquare, Activity,
  Lock, Flame, FileCheck, User, Users, UserCog, Play, Square, Package,
  ChevronUp, Check, X, Home, Info, Plus, Trash2, Crosshair,
} from 'lucide-react';
import { useAppStore } from '@/stores/app';
import { useI18n } from '@/i18n';
import { explorerApi, type CompartmentNode, type Region, type ResourceItem } from '@/api/endpoints/explorer';

/* ══════════════════════════════════════════════════════════════════════════════
   Constants
   ══════════════════════════════════════════════════════════════════════════════ */

const ICON_MAP: Record<string, React.ElementType> = {
  Server, HardDrive, Layers, Network, GitBranch, Shield, ShieldCheck,
  Route, ArrowUpRight, Globe, Plug, MapPin, Scale, Database, FolderOpen,
  Link, Box, Boxes, Zap, Webhook, Bell, FileText, MessageSquare, Activity,
  Lock, Flame, FileCheck, User, Users, UserCog,
};

interface ResType { key: string; label: string; icon: string }

const EXP_GROUPS: Record<string, ResType[]> = {
  'Compute': [
    { key: 'instances', label: 'Instances', icon: 'Server' },
    { key: 'boot_volumes', label: 'Boot Volumes', icon: 'HardDrive' },
    { key: 'instance_pools', label: 'Instance Pools', icon: 'Layers' },
  ],
  'Rede': [
    { key: 'vcns', label: 'VCNs', icon: 'Network' },
    { key: 'subnets', label: 'Subnets', icon: 'GitBranch' },
    { key: 'security_lists', label: 'Security Lists', icon: 'Shield' },
    { key: 'nsgs', label: 'NSGs', icon: 'ShieldCheck' },
    { key: 'route_tables', label: 'Route Tables', icon: 'Route' },
    { key: 'nat_gateways', label: 'NAT Gateways', icon: 'ArrowUpRight' },
    { key: 'internet_gateways', label: 'Internet Gateways', icon: 'Globe' },
    { key: 'service_gateways', label: 'Service Gateways', icon: 'Plug' },
    { key: 'public_ips', label: 'Public IPs', icon: 'MapPin' },
    { key: 'load_balancers', label: 'Load Balancers', icon: 'Scale' },
  ],
  'Storage': [
    { key: 'buckets', label: 'Buckets', icon: 'Database' },
    { key: 'block_volumes', label: 'Block Volumes', icon: 'HardDrive' },
    { key: 'file_systems', label: 'File Systems', icon: 'FolderOpen' },
    { key: 'mount_targets', label: 'Mount Targets', icon: 'Link' },
  ],
  'Database': [
    { key: 'databases', label: 'Autonomous DB', icon: 'Database' },
    { key: 'db_systems', label: 'DB Systems', icon: 'Server' },
    { key: 'mysql_db_systems', label: 'MySQL', icon: 'Database' },
  ],
  'Containers': [
    { key: 'container_instances', label: 'Container Instances', icon: 'Box' },
    { key: 'oke_clusters', label: 'OKE Clusters', icon: 'Boxes' },
  ],
  'Serverless': [
    { key: 'functions', label: 'Functions', icon: 'Zap' },
    { key: 'api_gateways', label: 'API Gateways', icon: 'Webhook' },
  ],
  'Observabilidade': [
    { key: 'alarms', label: 'Alarms', icon: 'Bell' },
    { key: 'log_groups', label: 'Log Groups', icon: 'FileText' },
    { key: 'notification_topics', label: 'Notification Topics', icon: 'MessageSquare' },
    { key: 'events_rules', label: 'Events Rules', icon: 'Activity' },
  ],
  'Seguranca': [
    { key: 'vaults', label: 'Vaults', icon: 'Lock' },
    { key: 'dns_zones', label: 'DNS Zones', icon: 'Globe' },
    { key: 'network_firewalls', label: 'Network Firewalls', icon: 'Flame' },
    { key: 'network_firewall_policies', label: 'Firewall Policies', icon: 'FileCheck' },
  ],
  'IAM': [
    { key: 'iam_users', label: 'Users', icon: 'User' },
    { key: 'iam_groups', label: 'Groups', icon: 'Users' },
    { key: 'iam_policies', label: 'Policies', icon: 'FileText' },
    { key: 'iam_dynamic_groups', label: 'Dynamic Groups', icon: 'UserCog' },
  ],
};

const CATEGORIES = Object.keys(EXP_GROUPS);

const CATEGORY_ICONS: Record<string, React.ElementType> = {
  Compute: Server, Rede: Network, Storage: Database, Database: Database,
  Containers: Box, Serverless: Zap, Observabilidade: Bell, Seguranca: Lock, IAM: Users,
};

const CATEGORY_I18N: Record<string, string> = {
  Rede: 'exp.network',
  Observabilidade: 'exp.observability',
  Seguranca: 'exp.security',
};

const STARTABLE: Record<string, { running: string[]; stopped: string[]; actionType: string }> = {
  instances:           { running: ['RUNNING'], stopped: ['STOPPED'], actionType: 'instance' },
  databases:           { running: ['AVAILABLE'], stopped: ['STOPPED'], actionType: 'adb' },
  db_systems:          { running: ['AVAILABLE'], stopped: ['STOPPED'], actionType: 'dbSystem' },
  mysql_db_systems:    { running: ['ACTIVE'], stopped: ['INACTIVE'], actionType: 'mysql' },
  container_instances: { running: ['ACTIVE'], stopped: ['INACTIVE'], actionType: 'container' },
};

/* ══════════════════════════════════════════════════════════════════════════════
   Helpers
   ══════════════════════════════════════════════════════════════════════════════ */

interface TreeNode extends CompartmentNode {
  children: TreeNode[];
  depth: number;
}

function buildTree(flat: CompartmentNode[]): TreeNode[] {
  // Same logic as legacy rExpTree: filter by parent_id, recursive
  const byParent = new Map<string, CompartmentNode[]>();
  for (const n of flat) {
    const key = n.parent_id ?? '__root__';
    const arr = byParent.get(key) || [];
    arr.push(n);
    byParent.set(key, arr);
  }
  const build = (parentId: string | null, depth: number): TreeNode[] => {
    const key = parentId ?? '__root__';
    const children = byParent.get(key) || [];
    return children.map((n) => ({
      ...n,
      depth,
      children: build(n.id, depth + 1),
    }));
  };
  return build(null, 0);
}

function formatBytes(bytes: number | null | undefined): string {
  if (bytes == null || bytes === 0) return '0 B';
  const units = ['B', 'KB', 'MB', 'GB', 'TB'];
  const i = Math.floor(Math.log(bytes) / Math.log(1024));
  return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${units[i]}`;
}

function lifecycleColor(state: string): { bg: string; text: string } {
  const s = (state || '').toUpperCase();
  if (['RUNNING', 'ACTIVE', 'AVAILABLE'].includes(s))
    return { bg: 'var(--gnl)', text: 'var(--gn)' };
  if (['STOPPED', 'INACTIVE'].includes(s))
    return { bg: 'var(--yll)', text: 'var(--yl)' };
  if (['TERMINATED', 'DELETED', 'DELETING', 'TERMINATING'].includes(s))
    return { bg: 'var(--rdl)', text: 'var(--rd)' };
  return { bg: 'var(--bg3)', text: 'var(--t3)' };
}

function truncId(id: string | undefined): string {
  if (!id) return '';
  if (id.length <= 30) return id;
  return id.slice(0, 14) + '...' + id.slice(-14);
}

/* ══════════════════════════════════════════════════════════════════════════════
   Sub-components
   ══════════════════════════════════════════════════════════════════════════════ */

/* ── Lifecycle Badge ──────────────────────────────────────────────────────── */
function StateBadge({ state }: { state: string }) {
  const c = lifecycleColor(state);
  return (
    <span
      className="px-2 py-0.5 rounded text-xs font-semibold whitespace-nowrap"
      style={{ background: c.bg, color: c.text }}
    >
      {state}
    </span>
  );
}

/* ── KV Row (generic card helper) ─────────────────────────────────────────── */
function KV({ label, value, mono }: { label: string; value: React.ReactNode; mono?: boolean }) {
  return (
    <div className="flex gap-2 text-xs leading-5">
      <span className="shrink-0 font-medium" style={{ color: 'var(--t3)', minWidth: 110 }}>{label}</span>
      <span className={mono ? 'font-mono' : ''} style={{ color: 'var(--t1)', wordBreak: 'break-all' }}>
        {value ?? '—'}
      </span>
    </div>
  );
}

/* ── Compartment Tree Node ────────────────────────────────────────────────── */
function TreeItem({
  node, selected, expanded, onSelect, onToggle,
}: {
  node: TreeNode;
  selected: string;
  expanded: Record<string, boolean>;
  onSelect: (id: string) => void;
  onToggle: (id: string) => void;
}) {
  const hasKids = node.children.length > 0;
  const isOpen = expanded[node.id];
  const isSel = selected === node.id;

  return (
    <div>
      <div
        className="flex items-center gap-1.5 rounded-md cursor-pointer transition-all"
        style={{
          padding: '.35rem .6rem',
          fontSize: '.76rem',
          background: isSel ? 'var(--acl)' : undefined,
          color: isSel ? 'var(--ac)' : 'var(--t2)',
          fontWeight: isSel ? 600 : 400,
          borderLeft: isSel ? '2px solid var(--ac)' : '2px solid transparent',
        }}
        onClick={() => { onSelect(node.id); if (hasKids && !isOpen) onToggle(node.id); }}
        onMouseEnter={(e) => { if (!isSel) e.currentTarget.style.background = 'var(--bg2)'; }}
        onMouseLeave={(e) => { if (!isSel) e.currentTarget.style.background = ''; }}
      >
        <span
          className="shrink-0 cursor-pointer"
          style={{ width: 16, textAlign: 'center', fontSize: '.55rem', color: 'var(--t4)' }}
          onClick={(e) => { e.stopPropagation(); if (hasKids) onToggle(node.id); }}
        >
          {hasKids ? (isOpen ? '▼' : '▶') : ' '}
        </span>
        <span className="truncate" title={node.name}>
          {node.name}
        </span>
      </div>
      {hasKids && isOpen && (
        <div style={{ paddingLeft: '.85rem', borderLeft: '1px solid var(--bg4)', marginLeft: '.65rem' }}>
          {node.children.map((c) => (
            <TreeItem key={c.id} node={c} selected={selected} expanded={expanded}
              onSelect={onSelect} onToggle={onToggle} />
          ))}
        </div>
      )}
    </div>
  );
}

/* ── Region Multi-Select ──────────────────────────────────────────────────── */
function RegionSelector({
  regions, selected, onChange,
}: {
  regions: Region[];
  selected: string[];
  onChange: (v: string[]) => void;
}) {
  const { t } = useI18n();
  const [open, setOpen] = useState(false);
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const handler = (e: MouseEvent) => {
      if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);
    };
    document.addEventListener('mousedown', handler);
    return () => document.removeEventListener('mousedown', handler);
  }, []);

  const toggle = (name: string) => {
    onChange(selected.includes(name) ? selected.filter((r) => r !== name) : [...selected, name]);
  };

  return (
    <div ref={ref} className="relative">
      <button
        className="w-full flex items-center justify-between gap-1 px-2 py-1.5 rounded text-xs border"
        style={{ background: 'var(--bg2)', borderColor: 'var(--bd)', color: 'var(--t1)' }}
        onClick={() => setOpen(!open)}
      >
        <span>{selected.length ? t('exp.nRegions').replace('{0}', String(selected.length)) : t('exp.selectRegions')}</span>
        {open ? <ChevronUp size={14} /> : <ChevronDown size={14} />}
      </button>
      {open && (
        <div
          className="absolute z-50 mt-1 w-full rounded border shadow-lg max-h-56 overflow-y-auto"
          style={{ background: 'var(--bg1)', borderColor: 'var(--bd)', boxShadow: 'var(--sh2)' }}
        >
          <div className="flex gap-1 p-1 border-b" style={{ borderColor: 'var(--bd)' }}>
            <button
              className="flex-1 text-xs py-0.5 rounded"
              style={{ background: 'var(--bg3)', color: 'var(--t2)' }}
              onClick={() => onChange(regions.map((r) => r.name))}
            >{t('exp.all')}</button>
            <button
              className="flex-1 text-xs py-0.5 rounded"
              style={{ background: 'var(--bg3)', color: 'var(--t2)' }}
              onClick={() => onChange([])}
            >{t('exp.none')}</button>
          </div>
          {regions.map((r) => (
            <label
              key={r.name}
              className="flex items-center gap-2 px-2 py-1 cursor-pointer hover:opacity-80 text-xs"
              style={{ color: 'var(--t1)' }}
            >
              <input
                type="checkbox"
                checked={selected.includes(r.name)}
                onChange={() => toggle(r.name)}
                className="accent-current"
                style={{ accentColor: 'var(--ac)' }}
              />
              <span className="flex-1">{r.name}</span>
              {r.is_home && <Home size={12} style={{ color: 'var(--ac)' }} />}
            </label>
          ))}
        </div>
      )}
    </div>
  );
}

/* ══════════════════════════════════════════════════════════════════════════════
   Special Card Renderers
   ══════════════════════════════════════════════════════════════════════════════ */

function VcnCard({ r }: { r: ResourceItem }) {
  const { t } = useI18n();
  return (
    <>
      <KV label="CIDR Blocks" value={(r.cidr_blocks || []).join(', ')} mono />
      <KV label="DNS Label" value={r.dns_label} mono />
      <KV label="Domain Name" value={r.vcn_domain_name} mono />
      <KV label="Default RT" value={truncId(r.default_route_table_id)} mono />
      <KV label="Default SL" value={truncId(r.default_security_list_id)} mono />
      <KV label={t('exp.createdAt')} value={r.time_created} />
    </>
  );
}

function SubnetCard({ r }: { r: ResourceItem }) {
  const { t } = useI18n();
  const slIds = (r.security_list_ids || []) as string[];
  return (
    <>
      <KV label="CIDR" value={r.cidr_block} mono />
      <KV label="AD" value={r.availability_domain || 'Regional'} />
      <KV label="DNS Label" value={r.dns_label} mono />
      <KV label="Domain" value={r.subnet_domain_name} mono />
      <KV label={t('exp.public')} value={r.prohibit_public_ip_on_vnic ? t('exp.noPrivate') : t('exp.yesPublic')} />
      <KV label="VCN" value={truncId(r.vcn_id)} mono />
      <KV label="Route Table" value={truncId(r.route_table_id)} mono />
      {slIds.length > 0 && (
        <div className="flex gap-2 text-xs leading-5">
          <span className="shrink-0 font-medium" style={{ color: 'var(--t3)', minWidth: 110 }}>Security Lists</span>
          <div className="flex flex-col gap-0.5">
            {slIds.map((id, i) => (
              <span key={i} className="font-mono text-[.68rem]" style={{ color: 'var(--t1)', wordBreak: 'break-all' }}>{truncId(id)}</span>
            ))}
          </div>
        </div>
      )}
      <KV label={t('exp.createdAt')} value={r.time_created} />
    </>
  );
}

function InstanceCard({ r }: { r: ResourceItem }) {
  const { t } = useI18n();
  return (
    <>
      <KV label="Shape" value={r.shape} />
      <KV label={t('exp.region')} value={r.region} />
      <KV label={t('exp.createdAt')} value={r.time_created} />
    </>
  );
}

function AdbCard({ r, configId, regions, onReload }: { r: ResourceItem; configId: string; regions: string[]; onReload: () => void }) {
  const { t } = useI18n();
  const [updating, setUpdating] = useState(false);
  const [msg, setMsg] = useState<{ type: 's' | 'e'; text: string } | null>(null);
  const ips = (r.whitelisted_ips || []) as string[];
  const region = regions.length === 1 ? regions[0] : undefined;

  const handleUpdateNetwork = async () => {
    setMsg(null);
    try {
      const resp = await fetch('https://api.ipify.org?format=json');
      const data = await resp.json();
      const myIp = data.ip + '/32';
      const ip = prompt('IP para liberar acesso (CIDR):', myIp);
      if (!ip) return;
      setUpdating(true);
      await explorerApi.adbUpdateNetwork(r.id, ip.trim(), configId, region);
      setMsg({ type: 's', text: t('exp.accessUpdated') + ip.trim() });
      onReload();
    } catch (e) {
      setMsg({ type: 'e', text: (e as Error).message });
    } finally {
      setUpdating(false);
    }
  };

  return (
    <>
      <KV label="DB Name" value={r.db_name} mono />
      <KV label="CPUs" value={r.cpu_core_count} />
      <KV label="Storage" value={r.data_storage_size_in_tbs ? `${r.data_storage_size_in_tbs} TB` : '—'} />
      <KV label={t('exp.freeTier')} value={r.is_free_tier ? t('exp.yes') : t('exp.no')} />
      {ips.length > 0 && (
        <div className="flex gap-2 text-xs leading-5">
          <span className="shrink-0 font-medium" style={{ color: 'var(--t3)', minWidth: 110 }}>ACL</span>
          <div className="flex flex-wrap gap-1">
            {ips.map((ip, i) => (
              <span key={i} className="px-1.5 py-0.5 rounded text-[.65rem] font-mono" style={{ background: 'var(--acl)', color: 'var(--ac)' }}>{ip}</span>
            ))}
          </div>
        </div>
      )}
      {ips.length === 0 && <KV label="ACL" value={t('exp.noRestrictions')} />}
      <div className="flex items-center gap-2 mt-1">
        <button
          onClick={handleUpdateNetwork}
          disabled={updating}
          className="flex items-center gap-1 text-xs px-2 py-0.5 rounded"
          style={{ background: 'var(--acl)', color: 'var(--ac)' }}
        >
          {updating ? <Loader2 size={10} className="animate-spin" /> : <Crosshair size={10} />}
          {t('exp.updateNetworkAccess')}
        </button>
      </div>
      {msg && <div className="text-xs px-2 py-1 mt-1 rounded" style={{ background: msg.type === 's' ? 'var(--gnl)' : 'var(--rdl)', color: msg.type === 's' ? 'var(--gn)' : 'var(--rd)' }}>{msg.text}</div>}
    </>
  );
}

function DbSystemCard({ r }: { r: ResourceItem }) {
  return (
    <>
      <KV label="Shape" value={r.shape} />
      <KV label="Edition" value={r.database_edition} />
      <KV label="CPUs" value={r.cpu_core_count} />
      <KV label="Nodes" value={r.node_count} />
    </>
  );
}

function MysqlCard({ r }: { r: ResourceItem }) {
  const { t } = useI18n();
  return (
    <>
      <KV label="Shape" value={r.shape_name} />
      <KV label={t('exp.version')} value={r.mysql_version} />
      <KV label={t('exp.ha')} value={r.is_highly_available ? t('exp.yes') : t('exp.no')} />
    </>
  );
}

function ContainerCard({ r }: { r: ResourceItem }) {
  const { t } = useI18n();
  return (
    <>
      <KV label="Shape" value={r.shape} />
      <KV label="Containers" value={r.container_count} />
      <KV label={t('exp.createdAt')} value={r.time_created} />
    </>
  );
}

function BucketCard({ r }: { r: ResourceItem }) {
  const { t } = useI18n();
  return (
    <>
      <KV label="Namespace" value={r.namespace} mono />
      <KV label={t('exp.objects')} value={r.approximate_count?.toLocaleString() ?? '—'} />
      <KV label={t('exp.size')} value={formatBytes(r.approximate_size)} />
      <KV label={t('exp.visibility')} value={r.public_access_type} />
      <KV label="Versioning" value={r.versioning || 'Disabled'} />
      <KV label="Storage Tier" value={r.storage_tier} />
      <KV label={t('exp.createdAt')} value={r.time_created} />
    </>
  );
}

function LoadBalancerCard({ r }: { r: ResourceItem }) {
  const { t } = useI18n();
  return (
    <>
      <KV label="Shape" value={r.shape_name} />
      <KV label="IPs" value={(r.ip_addresses || []).join(', ')} mono />
      <KV label={t('exp.private')} value={r.is_private ? t('exp.yes') : t('exp.no')} />
      {(r.listeners || []).length > 0 && (
        <div className="mt-2">
          <span className="text-xs font-semibold" style={{ color: 'var(--t2)' }}>Listeners</span>
          <div className="mt-1 overflow-x-auto">
            <table className="w-full text-xs" style={{ color: 'var(--t2)' }}>
              <thead>
                <tr style={{ borderBottom: '1px solid var(--bd)' }}>
                  <th className="text-left py-1 pr-2">{t('exp.description')}</th>
                  <th className="text-left py-1 pr-2">{t('exp.ports')}</th>
                  <th className="text-left py-1 pr-2">{t('exp.protocol')}</th>
                  <th className="text-left py-1">Backend Set</th>
                </tr>
              </thead>
              <tbody>
                {(r.listeners as Array<{name:string;port:number;protocol:string;backend_set:string}>).map((l, i) => (
                  <tr key={i} style={{ borderBottom: '1px solid var(--bg3)' }}>
                    <td className="py-1 pr-2 font-mono">{l.name}</td>
                    <td className="py-1 pr-2">{l.port}</td>
                    <td className="py-1 pr-2">{l.protocol}</td>
                    <td className="py-1">{l.backend_set}</td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        </div>
      )}
      {(r.backend_sets || []).length > 0 && (
        <div className="mt-2">
          <span className="text-xs font-semibold" style={{ color: 'var(--t2)' }}>Backend Sets</span>
          {(r.backend_sets as Array<{name:string;policy:string;backends:Array<{ip:string;port:number;weight:number}>}>).map((bs, i) => (
            <div key={i} className="mt-1 pl-2 border-l-2" style={{ borderColor: 'var(--bg4)' }}>
              <span className="text-xs font-mono" style={{ color: 'var(--t1)' }}>{bs.name}</span>
              <span className="text-xs ml-2" style={{ color: 'var(--t3)' }}>({bs.policy})</span>
              {(bs.backends || []).map((b, j) => (
                <div key={j} className="text-xs pl-2 font-mono" style={{ color: 'var(--t3)' }}>
                  {b.ip}:{b.port} (w={b.weight})
                </div>
              ))}
            </div>
          ))}
        </div>
      )}
    </>
  );
}

/* ── Rule Form Helpers ─────────────────────────────────────────────────────── */

function RuleSelect({ label, value, onChange, options }: { label: string; value: string; onChange: (v: string) => void; options: string[][] }) {
  return (
    <div>
      <span className="block text-[.65rem] font-semibold mb-0.5" style={{ color: 'var(--t3)' }}>{label}</span>
      <select value={value} onChange={(e) => onChange(e.target.value)} className="w-full px-2 py-1 rounded text-xs border outline-none" style={{ background: 'var(--bg)', borderColor: 'var(--bd)', color: 'var(--t1)' }}>
        {options.map(([v, l]) => <option key={v} value={v}>{l}</option>)}
      </select>
    </div>
  );
}

function RuleInput({ label, value, onChange, placeholder }: { label: string; value: string; onChange: (v: string) => void; placeholder?: string }) {
  return (
    <div className="flex-1">
      <span className="block text-[.65rem] font-semibold mb-0.5" style={{ color: 'var(--t3)' }}>{label}</span>
      <input type="text" value={value} onChange={(e) => onChange(e.target.value)} placeholder={placeholder} className="w-full px-2 py-1 rounded text-xs border outline-none" style={{ background: 'var(--bg)', borderColor: 'var(--bd)', color: 'var(--t1)' }} />
    </div>
  );
}

function SecurityListCard({ r, configId, regions, onReload }: { r: ResourceItem; configId: string; regions: string[]; onReload: () => void }) {
  const { t } = useI18n();
  const [expanded, setExpanded] = useState(false);
  const [showForm, setShowForm] = useState(false);
  const [formMsg, setFormMsg] = useState<{ type: 's' | 'e'; text: string } | null>(null);
  const [saving, setSaving] = useState(false);
  const [dir, setDir] = useState('ingress');
  const [proto, setProto] = useState('6');
  const [cidr, setCidr] = useState('');
  const [portMin, setPortMin] = useState('');
  const [portMax, setPortMax] = useState('');
  const [desc, setDesc] = useState('');
  const [stateless, setStateless] = useState(false);

  const rules = (r.rules || []) as Array<{
    direction: string; protocol: string; source_dest: string;
    ports: string; stateless: boolean; description: string; rule_index?: number;
  }>;
  const ingress = rules.filter((x) => x.direction === 'ingress');
  const egress = rules.filter((x) => x.direction === 'egress');
  const region = regions.length === 1 ? regions[0] : undefined;

  const handleAdd = async () => {
    if (!cidr.trim()) { setFormMsg({ type: 'e', text: t('exp.enterCidr') }); return; }
    setSaving(true); setFormMsg(null);
    try {
      const body: Record<string, unknown> = { direction: dir, protocol: proto, source_dest: cidr.trim(), description: desc, is_stateless: stateless, region };
      if (proto !== 'all' && proto !== '1' && portMin) { body.port_min = parseInt(portMin); body.port_max = parseInt(portMax || portMin); }
      await explorerApi.addSlRule(configId, r.id, body as Parameters<typeof explorerApi.addSlRule>[2]);
      setFormMsg({ type: 's', text: t('exp.ruleAdded') }); setShowForm(false); setCidr(''); setPortMin(''); setPortMax(''); setDesc('');
      onReload();
    } catch (e) { setFormMsg({ type: 'e', text: (e as Error).message }); }
    finally { setSaving(false); }
  };

  const handleRemove = async (direction: string, ruleIndex: number) => {
    if (!confirm(t('exp.removeRule'))) return;
    try {
      await explorerApi.removeSlRule(configId, r.id, { direction, rule_index: ruleIndex, region });
      onReload();
    } catch (e) { alert((e as Error).message); }
  };

  const handleMyIp = async () => {
    try {
      const resp = await fetch('https://api.ipify.org?format=json');
      const data = await resp.json();
      const myIp = data.ip + '/32';
      setCidr(myIp); setDesc('Acesso temporário - ' + myIp);
      setFormMsg({ type: 's', text: t('exp.ipDetected').replace('{0}', myIp) });
    } catch { setFormMsg({ type: 'e', text: t('exp.ipDetectFailed') }); }
  };

  return (
    <>
      <KV label="VCN ID" value={truncId(r.vcn_id)} mono />
      <KV label="Ingress Rules" value={r.ingress_count} />
      <KV label="Egress Rules" value={r.egress_count} />
      <div className="flex items-center gap-2 mt-1">
        {rules.length > 0 && (
          <button className="text-xs underline" style={{ color: 'var(--ac)' }} onClick={() => setExpanded(!expanded)}>
            {expanded ? t('exp.hideRules') : t('exp.viewRules')}
          </button>
        )}
        <button
          className="flex items-center gap-1 text-xs px-2 py-0.5 rounded"
          style={{ background: 'var(--gnl)', color: 'var(--gn)' }}
          onClick={() => { setShowForm(!showForm); setFormMsg(null); }}
        >
          <Plus size={10} /> {t('exp.add')}
        </button>
      </div>
      {showForm && (
        <div className="mt-2 p-2.5 rounded-lg space-y-2" style={{ background: 'var(--bg2)', border: '1px solid var(--bd)' }}>
          <div className="grid grid-cols-2 gap-2">
            <RuleSelect label={t('exp.direction')} value={dir} onChange={setDir} options={[['ingress','Ingress'],['egress','Egress']]} />
            <RuleSelect label={t('exp.protocol')} value={proto} onChange={setProto} options={[['6','TCP'],['17','UDP'],['1','ICMP'],['all','ALL']]} />
          </div>
          <div className="flex gap-2 items-end">
            <RuleInput label="CIDR" value={cidr} onChange={setCidr} placeholder="0.0.0.0/0" />
            <button onClick={handleMyIp} className="flex items-center gap-1 px-2 py-1.5 rounded text-[.68rem] font-medium shrink-0" style={{ background: 'var(--acl)', color: 'var(--ac)' }} title={t('exp.myIp')}>
              <Crosshair size={11} /> {t('exp.myIp')}
            </button>
          </div>
          {proto !== 'all' && proto !== '1' && (
            <div className="grid grid-cols-2 gap-2">
              <RuleInput label={t('exp.portMin')} value={portMin} onChange={setPortMin} placeholder="22" />
              <RuleInput label={t('exp.portMax')} value={portMax} onChange={setPortMax} placeholder="22" />
            </div>
          )}
          <RuleInput label={t('exp.description')} value={desc} onChange={setDesc} placeholder="Opcional" />
          <label className="flex items-center gap-1.5 text-xs cursor-pointer" style={{ color: 'var(--t2)' }}>
            <input type="checkbox" checked={stateless} onChange={(e) => setStateless(e.target.checked)} className="accent-current" style={{ accentColor: 'var(--ac)' }} />
            Stateless
          </label>
          {formMsg && <div className="text-xs px-2 py-1 rounded" style={{ background: formMsg.type === 's' ? 'var(--gnl)' : 'var(--rdl)', color: formMsg.type === 's' ? 'var(--gn)' : 'var(--rd)' }}>{formMsg.text}</div>}
          <div className="flex gap-2">
            <button onClick={handleAdd} disabled={saving} className="flex items-center gap-1 px-3 py-1 rounded text-xs font-medium" style={{ background: 'var(--gn)', color: '#fff' }}>
              {saving ? <Loader2 size={12} className="animate-spin" /> : <Check size={12} />} {t('exp.save')}
            </button>
            <button onClick={() => setShowForm(false)} className="px-3 py-1 rounded text-xs" style={{ background: 'var(--bg3)', color: 'var(--t2)' }}>{t('exp.cancel')}</button>
          </div>
        </div>
      )}
      {expanded && (
        <div className="mt-2 space-y-2 overflow-x-auto">
          {[{ label: 'Ingress', dir: 'ingress', data: ingress }, { label: 'Egress', dir: 'egress', data: egress }].map(({ label, dir: d, data }) =>
            data.length > 0 && (
              <div key={label}>
                <span className="text-xs font-semibold" style={{ color: 'var(--t2)' }}>{label}</span>
                <table className="w-full text-xs mt-1" style={{ color: 'var(--t2)' }}>
                  <thead>
                    <tr style={{ borderBottom: '1px solid var(--bd)' }}>
                      <th className="text-left py-1 pr-2">{t('exp.protocol')}</th>
                      <th className="text-left py-1 pr-2">{t('exp.originDest')}</th>
                      <th className="text-left py-1 pr-2">{t('exp.ports')}</th>
                      <th className="text-left py-1 pr-2">Stateless</th>
                      <th className="text-left py-1 pr-1">{t('exp.description')}</th>
                      <th className="w-6"></th>
                    </tr>
                  </thead>
                  <tbody>
                    {data.map((rule, i) => (
                      <tr key={i} style={{ borderBottom: '1px solid var(--bg3)' }}>
                        <td className="py-1 pr-2">{rule.protocol}</td>
                        <td className="py-1 pr-2 font-mono">{rule.source_dest || '*'}</td>
                        <td className="py-1 pr-2">{rule.ports || 'ALL'}</td>
                        <td className="py-1 pr-2">{rule.stateless ? t('exp.yes') : t('exp.no')}</td>
                        <td className="py-1 pr-1 truncate max-w-[150px]" title={rule.description}>{rule.description || '—'}</td>
                        <td className="py-1">
                          <button onClick={() => handleRemove(d, i)} className="p-0.5 rounded hover:opacity-80" style={{ color: 'var(--rd)' }} title={t('common.delete')}>
                            <Trash2 size={11} />
                          </button>
                        </td>
                      </tr>
                    ))}
                  </tbody>
                </table>
              </div>
            )
          )}
        </div>
      )}
    </>
  );
}

function NsgCard({ r, configId, regions, onReload }: { r: ResourceItem; configId: string; regions: string[]; onReload: () => void }) {
  const { t } = useI18n();
  const [expanded, setExpanded] = useState(false);
  const [showForm, setShowForm] = useState(false);
  const [formMsg, setFormMsg] = useState<{ type: 's' | 'e'; text: string } | null>(null);
  const [saving, setSaving] = useState(false);
  const [dir, setDir] = useState('INGRESS');
  const [proto, setProto] = useState('6');
  const [cidr, setCidr] = useState('');
  const [portMin, setPortMin] = useState('');
  const [portMax, setPortMax] = useState('');
  const [desc, setDesc] = useState('');
  const [stateless, setStateless] = useState(false);

  const rules = (r.rules || []) as Array<{
    id: string; direction: string; protocol: string;
    source_dest: string; source_dest_type: string;
    ports: string; is_stateless: boolean; description: string;
  }>;
  const region = regions.length === 1 ? regions[0] : undefined;

  const handleAdd = async () => {
    if (!cidr.trim()) { setFormMsg({ type: 'e', text: t('exp.enterCidr') }); return; }
    setSaving(true); setFormMsg(null);
    try {
      const body: Record<string, unknown> = { direction: dir, protocol: proto, source_dest: cidr.trim(), source_dest_type: 'CIDR_BLOCK', description: desc, is_stateless: stateless, region };
      if (proto !== 'all' && proto !== '1' && portMin) { body.port_min = parseInt(portMin); body.port_max = parseInt(portMax || portMin); }
      await explorerApi.addNsgRule(configId, r.id, body as Parameters<typeof explorerApi.addNsgRule>[2]);
      setFormMsg({ type: 's', text: t('exp.ruleAdded') }); setShowForm(false); setCidr(''); setPortMin(''); setPortMax(''); setDesc('');
      onReload();
    } catch (e) { setFormMsg({ type: 'e', text: (e as Error).message }); }
    finally { setSaving(false); }
  };

  const handleRemove = async (ruleId: string) => {
    if (!confirm(t('exp.removeNsgRule'))) return;
    try {
      await explorerApi.removeNsgRule(configId, r.id, ruleId, region);
      onReload();
    } catch (e) { alert((e as Error).message); }
  };

  const handleMyIp = async () => {
    try {
      const resp = await fetch('https://api.ipify.org?format=json');
      const data = await resp.json();
      const myIp = data.ip + '/32';
      setCidr(myIp); setDesc('Acesso temporário - ' + myIp);
      setFormMsg({ type: 's', text: t('exp.ipDetected').replace('{0}', myIp) });
    } catch { setFormMsg({ type: 'e', text: t('exp.ipDetectFailed') }); }
  };

  return (
    <>
      <KV label="VCN ID" value={truncId(r.vcn_id)} mono />
      <KV label={t('exp.rules')} value={r.rule_count} />
      <KV label={t('exp.createdAt')} value={r.time_created} />
      <div className="flex items-center gap-2 mt-1">
        {rules.length > 0 && (
          <button className="text-xs underline" style={{ color: 'var(--ac)' }} onClick={() => setExpanded(!expanded)}>
            {expanded ? t('exp.hideRules') : t('exp.viewRules')}
          </button>
        )}
        <button
          className="flex items-center gap-1 text-xs px-2 py-0.5 rounded"
          style={{ background: 'var(--gnl)', color: 'var(--gn)' }}
          onClick={() => { setShowForm(!showForm); setFormMsg(null); }}
        >
          <Plus size={10} /> {t('exp.add')}
        </button>
      </div>
      {showForm && (
        <div className="mt-2 p-2.5 rounded-lg space-y-2" style={{ background: 'var(--bg2)', border: '1px solid var(--bd)' }}>
          <div className="grid grid-cols-2 gap-2">
            <RuleSelect label={t('exp.direction')} value={dir} onChange={setDir} options={[['INGRESS','Ingress'],['EGRESS','Egress']]} />
            <RuleSelect label={t('exp.protocol')} value={proto} onChange={setProto} options={[['6','TCP'],['17','UDP'],['1','ICMP'],['all','ALL']]} />
          </div>
          <div className="flex gap-2 items-end">
            <RuleInput label="CIDR" value={cidr} onChange={setCidr} placeholder="0.0.0.0/0" />
            <button onClick={handleMyIp} className="flex items-center gap-1 px-2 py-1.5 rounded text-[.68rem] font-medium shrink-0" style={{ background: 'var(--acl)', color: 'var(--ac)' }} title={t('exp.myIp')}>
              <Crosshair size={11} /> {t('exp.myIp')}
            </button>
          </div>
          {proto !== 'all' && proto !== '1' && (
            <div className="grid grid-cols-2 gap-2">
              <RuleInput label={t('exp.portMin')} value={portMin} onChange={setPortMin} placeholder="22" />
              <RuleInput label={t('exp.portMax')} value={portMax} onChange={setPortMax} placeholder="22" />
            </div>
          )}
          <RuleInput label={t('exp.description')} value={desc} onChange={setDesc} placeholder="Opcional" />
          <label className="flex items-center gap-1.5 text-xs cursor-pointer" style={{ color: 'var(--t2)' }}>
            <input type="checkbox" checked={stateless} onChange={(e) => setStateless(e.target.checked)} className="accent-current" style={{ accentColor: 'var(--ac)' }} />
            Stateless
          </label>
          {formMsg && <div className="text-xs px-2 py-1 rounded" style={{ background: formMsg.type === 's' ? 'var(--gnl)' : 'var(--rdl)', color: formMsg.type === 's' ? 'var(--gn)' : 'var(--rd)' }}>{formMsg.text}</div>}
          <div className="flex gap-2">
            <button onClick={handleAdd} disabled={saving} className="flex items-center gap-1 px-3 py-1 rounded text-xs font-medium" style={{ background: 'var(--gn)', color: '#fff' }}>
              {saving ? <Loader2 size={12} className="animate-spin" /> : <Check size={12} />} {t('exp.save')}
            </button>
            <button onClick={() => setShowForm(false)} className="px-3 py-1 rounded text-xs" style={{ background: 'var(--bg3)', color: 'var(--t2)' }}>{t('exp.cancel')}</button>
          </div>
        </div>
      )}
      {expanded && (
        <div className="mt-2 overflow-x-auto">
          <table className="w-full text-xs" style={{ color: 'var(--t2)' }}>
            <thead>
              <tr style={{ borderBottom: '1px solid var(--bd)' }}>
                <th className="text-left py-1 pr-2">{t('exp.direction')}</th>
                <th className="text-left py-1 pr-2">{t('exp.protocol')}</th>
                <th className="text-left py-1 pr-2">{t('exp.originDest')}</th>
                <th className="text-left py-1 pr-2">Tipo</th>
                <th className="text-left py-1 pr-2">{t('exp.ports')}</th>
                <th className="text-left py-1 pr-1">{t('exp.description')}</th>
                <th className="w-6"></th>
              </tr>
            </thead>
            <tbody>
              {rules.map((rule, i) => (
                <tr key={i} style={{ borderBottom: '1px solid var(--bg3)' }}>
                  <td className="py-1 pr-2">{rule.direction}</td>
                  <td className="py-1 pr-2">{rule.protocol}</td>
                  <td className="py-1 pr-2 font-mono">{rule.source_dest || '*'}</td>
                  <td className="py-1 pr-2">{rule.source_dest_type}</td>
                  <td className="py-1 pr-2">{rule.ports || 'ALL'}</td>
                  <td className="py-1 pr-1 truncate max-w-[140px]" title={rule.description}>{rule.description || '—'}</td>
                  <td className="py-1">
                    <button onClick={() => handleRemove(rule.id)} className="p-0.5 rounded hover:opacity-80" style={{ color: 'var(--rd)' }} title={t('common.delete')}>
                      <Trash2 size={11} />
                    </button>
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}
    </>
  );
}

function RouteTableCard({ r }: { r: ResourceItem }) {
  const { t } = useI18n();
  const routes = (r.routes || []) as Array<{
    destination: string; destination_type: string;
    target_id: string; target_type: string; description: string;
  }>;

  return (
    <>
      <KV label="VCN ID" value={truncId(r.vcn_id)} mono />
      <KV label={t('exp.routes')} value={r.route_count} />
      {routes.length > 0 && (
        <div className="mt-2 overflow-x-auto">
          <table className="w-full text-xs" style={{ color: 'var(--t2)' }}>
            <thead>
              <tr style={{ borderBottom: '1px solid var(--bd)' }}>
                <th className="text-left py-1 pr-2">{t('exp.destination')}</th>
                <th className="text-left py-1 pr-2">{t('exp.destType')}</th>
                <th className="text-left py-1 pr-2">Target Type</th>
                <th className="text-left py-1">{t('exp.description')}</th>
              </tr>
            </thead>
            <tbody>
              {routes.map((rt, i) => (
                <tr key={i} style={{ borderBottom: '1px solid var(--bg3)' }}>
                  <td className="py-1 pr-2 font-mono">{rt.destination}</td>
                  <td className="py-1 pr-2">{rt.destination_type}</td>
                  <td className="py-1 pr-2">{rt.target_type}</td>
                  <td className="py-1 truncate max-w-[180px]" title={rt.description}>{rt.description || '—'}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}
    </>
  );
}

/* ══════════════════════════════════════════════════════════════════════════════
   Main Page
   ══════════════════════════════════════════════════════════════════════════════ */

export default function ExplorerPage() {
  const { ociCfg } = useAppStore();
  const { t } = useI18n();

  /* ── State ──────────────────────────────────────────────────────────────── */
  const [selectedConfig, setSelectedConfig] = useState('');
  const [compartmentTree, setCompartmentTree] = useState<TreeNode[]>([]);
  const [selectedCompartment, setSelectedCompartment] = useState('');
  const [selectedRegions, setSelectedRegions] = useState<string[]>([]);
  const [availableRegions, setAvailableRegions] = useState<Region[]>([]);
  const [selectedCategory, setSelectedCategory] = useState('Compute');
  const [selectedResourceType, setSelectedResourceType] = useState('instances');
  const [resourceData, setResourceData] = useState<ResourceItem[] | null>(null);
  const [counts, setCounts] = useState<Record<string, number>>({});
  const [loading, setLoading] = useState(false);
  const [countsRefreshing, setCountsRefreshing] = useState(false);
  const [treeOpen, setTreeOpen] = useState<Record<string, boolean>>({});
  const [treeWidth, setTreeWidth] = useState(280);
  const [treeLoading, setTreeLoading] = useState(false);
  const [actionPending, setActionPending] = useState<Record<string, boolean>>({});
  const [searchFilter, setSearchFilter] = useState('');

  const containerRef = useRef<HTMLDivElement>(null);
  const draggingRef = useRef(false);

  /* ── Pick first config ──────────────────────────────────────────────────── */
  useEffect(() => {
    if (ociCfg.length > 0 && !selectedConfig) setSelectedConfig(ociCfg[0].id);
  }, [ociCfg, selectedConfig]);

  /* ── Load tree + regions on config change ───────────────────────────────── */
  useEffect(() => {
    if (!selectedConfig) return;
    let cancelled = false;
    setTreeLoading(true);
    setCompartmentTree([]);
    setSelectedCompartment('');
    setCounts({});
    setResourceData(null);

    (async () => {
      try {
        const [flat, regs] = await Promise.all([
          explorerApi.getCompartmentTree(selectedConfig),
          explorerApi.getRegions(selectedConfig),
        ]);
        if (cancelled) return;
        if (Array.isArray(flat)) {
          const tree = buildTree(flat);
          setCompartmentTree(tree);
          // Auto-select first node and expand it
          if (tree.length > 0) {
            setSelectedCompartment(tree[0].id);
            setTreeOpen((prev) => ({ ...prev, [tree[0].id]: true }));
          }
        }
        if (Array.isArray(regs)) {
          setAvailableRegions(regs);
          const home = regs.find((r) => r.is_home);
          setSelectedRegions(home ? [home.name] : regs.length > 0 ? [regs[0].name] : []);
        }
      } catch {
        /* ignore */
      } finally {
        if (!cancelled) setTreeLoading(false);
      }
    })();
    return () => { cancelled = true; };
  }, [selectedConfig]);

  /* ── Load counts when compartment or regions change ─────────────────────── */
  useEffect(() => {
    if (!selectedConfig || !selectedCompartment) return;
    let cancelled = false;
    const regionsKey = [...selectedRegions].sort().join(',');
    (async () => {
      try {
        const data = await explorerApi.getCounts(selectedConfig, selectedCompartment, regionsKey);
        if (cancelled) return;
        if (data && typeof data === 'object' && !('error' in data)) {
          const simple: Record<string, number> = {};
          for (const [k, v] of Object.entries(data)) {
            simple[k] = typeof v === 'object' && v !== null ? (v as { count: number }).count : (v as number);
          }
          setCounts(simple);
        }
      } catch { /* ignore */ }
    })();
    return () => { cancelled = true; };
  }, [selectedConfig, selectedCompartment, selectedRegions]);

  /* ── Load resources when type or compartment or regions change ───────────── */
  const loadResources = useCallback(async (resType?: string, silent = false) => {
    const rt = resType || selectedResourceType;
    if (!selectedConfig || !selectedCompartment) return;
    if (!silent) {
      setLoading(true);
      setResourceData(null);
    }

    try {
      // Load from all selected regions in batches of 3
      const regions = selectedRegions.length > 0 ? [...selectedRegions] : [undefined];
      const allItems: ResourceItem[] = [];

      for (let i = 0; i < regions.length; i += 3) {
        const batch = regions.slice(i, i + 3);
        const results = await Promise.all(
          batch.map((reg) =>
            explorerApi.getResources(selectedConfig, rt, selectedCompartment, reg)
              .catch(() => [])
          )
        );
        for (const res of results) {
          if (Array.isArray(res)) allItems.push(...res);
        }
        if (i + 3 < regions.length) {
          await new Promise((resolve) => setTimeout(resolve, 800));
        }
      }
      setResourceData(allItems);
    } catch {
      if (!silent) setResourceData([]);
    } finally {
      if (!silent) setLoading(false);
    }
  }, [selectedConfig, selectedCompartment, selectedRegions, selectedResourceType]);

  useEffect(() => {
    if (selectedCompartment && selectedResourceType) {
      loadResources();
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [selectedCompartment, selectedResourceType, selectedRegions]);

  /* ── Refresh Counts ─────────────────────────────────────────────────────── */
  const refreshCounts = useCallback(async () => {
    if (!selectedConfig || !selectedCompartment || countsRefreshing) return;
    setCountsRefreshing(true);
    try {
      await explorerApi.refreshCounts(selectedConfig, selectedCompartment, selectedRegions);
      // Poll
      let attempts = 0;
      const poll = setInterval(async () => {
        attempts++;
        const regionsKey = [...selectedRegions].sort().join(',');
        try {
          const data = await explorerApi.getCounts(selectedConfig, selectedCompartment, regionsKey);
          if (data && typeof data === 'object' && !('error' in data)) {
            const simple: Record<string, number> = {};
            for (const [k, v] of Object.entries(data)) {
              simple[k] = typeof v === 'object' && v !== null ? (v as { count: number }).count : (v as number);
            }
            setCounts(simple);
          }
        } catch { /* ignore */ }
        if (attempts >= 12) {
          clearInterval(poll);
          setCountsRefreshing(false);
        }
      }, 3000);
      // Also clear after max time
      setTimeout(() => { clearInterval(poll); setCountsRefreshing(false); }, 40000);
    } catch {
      setCountsRefreshing(false);
    }
  }, [selectedConfig, selectedCompartment, selectedRegions, countsRefreshing]);

  /* ── Start/Stop Action ──────────────────────────────────────────────────── */
  const handleAction = useCallback(async (item: ResourceItem, actionStr: string) => {
    const itemId = item.id || item.name;
    if (!itemId || actionPending[itemId]) return;

    setActionPending((p) => ({ ...p, [itemId]: true }));

    try {
      const cfg = STARTABLE[selectedResourceType];
      if (!cfg) return;

      switch (cfg.actionType) {
        case 'instance':
          await explorerApi.instanceAction(itemId, actionStr as 'START' | 'STOP', selectedConfig);
          break;
        case 'adb':
          await explorerApi.adbAction(itemId, actionStr.toLowerCase() as 'start' | 'stop', selectedConfig);
          break;
        case 'dbSystem':
          await explorerApi.dbSystemAction(itemId, actionStr as 'START' | 'STOP', selectedConfig);
          break;
        case 'mysql':
          await explorerApi.mysqlAction(itemId, actionStr.toLowerCase() as 'start' | 'stop', selectedConfig);
          break;
        case 'container':
          await explorerApi.containerAction(itemId, actionStr.toLowerCase() as 'start' | 'stop', selectedConfig);
          break;
      }

      // Poll for state change (silent = no flickering)
      let attempts = 0;
      const poll = setInterval(async () => {
        attempts++;
        try {
          await loadResources(undefined, true);
        } catch { /* ignore */ }
        if (attempts >= 12) {
          clearInterval(poll);
          setActionPending((p) => ({ ...p, [itemId]: false }));
        }
      }, 5000);
      setTimeout(() => { clearInterval(poll); setActionPending((p) => ({ ...p, [itemId]: false })); }, 65000);
    } catch {
      setActionPending((p) => ({ ...p, [itemId]: false }));
    }
  }, [selectedConfig, selectedResourceType, actionPending, loadResources]);

  /* ── Resizable tree panel ───────────────────────────────────────────────── */
  useEffect(() => {
    const onMove = (e: MouseEvent) => {
      if (!draggingRef.current || !containerRef.current) return;
      const rect = containerRef.current.getBoundingClientRect();
      const maxW = rect.width * 0.5;
      const newW = Math.max(140, Math.min(maxW, e.clientX - rect.left));
      setTreeWidth(newW);
    };
    const onUp = () => { draggingRef.current = false; document.body.style.cursor = ''; };
    document.addEventListener('mousemove', onMove);
    document.addEventListener('mouseup', onUp);
    return () => { document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); };
  }, []);

  /* ── Derived: KPI stats ─────────────────────────────────────────────────── */
  const kpiStats = useMemo(() => {
    const cats = CATEGORIES.filter((c) => c !== 'IAM');
    const catCounts = cats.map((cat) => {
      const types = EXP_GROUPS[cat];
      const total = types.reduce((sum, t) => sum + (counts[t.key] || 0), 0);
      return { cat, total };
    });
    catCounts.sort((a, b) => b.total - a.total);
    const top6 = catCounts.slice(0, 6);
    const grandTotal = Object.values(counts).reduce((s, v) => s + v, 0);
    return { top6, grandTotal };
  }, [counts]);

  /* ── Derived: filtered resources ────────────────────────────────────────── */
  const filteredResources = useMemo(() => {
    if (!resourceData) return null;
    if (!searchFilter.trim()) return resourceData;
    const q = searchFilter.toLowerCase();
    return resourceData.filter((r) => {
      const name = (r.display_name || r.name || '').toLowerCase();
      const id = (r.id || '').toLowerCase();
      return name.includes(q) || id.includes(q);
    });
  }, [resourceData, searchFilter]);

  /* ── Find compartment name ──────────────────────────────────────────────── */
  const selectedCompName = useMemo(() => {
    const find = (nodes: TreeNode[]): string | null => {
      for (const n of nodes) {
        if (n.id === selectedCompartment) return n.name;
        const c = find(n.children);
        if (c) return c;
      }
      return null;
    };
    return find(compartmentTree) || '—';
  }, [compartmentTree, selectedCompartment]);

  /* ── Category resource types with counts ────────────────────────────────── */
  const currentTypes = EXP_GROUPS[selectedCategory] || [];

  /* ══════════════════════════════════════════════════════════════════════════
     Render
     ══════════════════════════════════════════════════════════════════════════ */

  return (
    <div ref={containerRef} className="flex h-full overflow-hidden" style={{ background: 'var(--bg)' }}>
      {/* ── Left Panel: Tree ──────────────────────────────────────────────── */}
      <div
        className="flex flex-col shrink-0 border-r overflow-hidden"
        style={{ width: treeWidth, borderColor: 'var(--bd)', background: 'var(--bg1)' }}
      >
        {/* Header */}
        <div
          className="flex items-center gap-1.5 px-3 py-2 border-b shrink-0"
          style={{ borderColor: 'var(--bd)', background: 'var(--bg2)', fontSize: '.78rem', fontWeight: 700, color: 'var(--t1)' }}
        >
          <FolderTree size={14} style={{ color: 'var(--t3)' }} />
          Compartments
          {treeLoading && <Loader2 size={14} className="animate-spin ml-auto" style={{ color: 'var(--ac)' }} />}
        </div>

        {/* Compartment tree */}
        <div className="flex-1 overflow-y-auto py-1">
          {compartmentTree.length === 0 && !treeLoading && (
            <div className="px-3 py-4 text-xs text-center" style={{ color: 'var(--t4)' }}>
              {selectedConfig ? t('exp.noCompartment') : t('exp.selectConfig')}
            </div>
          )}
          {compartmentTree.map((node) => (
            <TreeItem
              key={node.id}
              node={node}
              selected={selectedCompartment}
              expanded={treeOpen}
              onSelect={setSelectedCompartment}
              onToggle={(id) => setTreeOpen((p) => ({ ...p, [id]: !p[id] }))}
            />
          ))}
        </div>
      </div>

      {/* ── Resize handle ─────────────────────────────────────────────────── */}
      <div
        className="w-1 shrink-0 cursor-col-resize hover:opacity-80 transition-colors"
        style={{ background: 'var(--bd)' }}
        onMouseDown={() => { draggingRef.current = true; document.body.style.cursor = 'col-resize'; }}
      />

      {/* ── Right Panel: Content ──────────────────────────────────────────── */}
      <div className="flex-1 flex flex-col overflow-hidden min-w-0">
        {/* Toolbar: OCI Config + Regions + Refresh */}
        <div
          className="flex items-center gap-2 px-3 py-2 border-b shrink-0 flex-wrap"
          style={{ borderColor: 'var(--bd)', background: 'var(--bg1)' }}
        >
          <select
            className="px-2 py-1.5 rounded text-xs border"
            style={{ background: 'var(--bg2)', borderColor: 'var(--bd)', color: 'var(--t1)' }}
            value={selectedConfig}
            onChange={(e) => setSelectedConfig(e.target.value)}
          >
            {ociCfg.map((c) => (
              <option key={c.id} value={c.id}>{c.tenancy_name} ({c.region})</option>
            ))}
          </select>

          <RegionSelector
            regions={availableRegions}
            selected={selectedRegions}
            onChange={setSelectedRegions}
          />

          {selectedRegions.length > 0 && (
            <div className="flex flex-wrap gap-1">
              {selectedRegions.map((r) => (
                <span
                  key={r}
                  className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-[10px] font-medium"
                  style={{ background: 'var(--acl)', color: 'var(--ac)', border: '1px solid var(--ac)' }}
                >
                  {r}
                  <button
                    className="font-bold text-[.7rem] cursor-pointer"
                    style={{ color: 'var(--ac)' }}
                    onClick={() => setSelectedRegions((prev) => prev.filter((x) => x !== r))}
                  >
                    &times;
                  </button>
                </span>
              ))}
            </div>
          )}

          <button
            className="flex items-center gap-1 px-2 py-1 rounded text-xs border transition-colors ml-auto"
            style={{ borderColor: 'var(--bd)', color: 'var(--t2)', background: 'var(--bg2)' }}
            onClick={() => {
              setCompartmentTree([]);
              setCounts({});
              setTreeLoading(true);
              explorerApi.getCompartmentTree(selectedConfig).then((flat) => {
                if (!Array.isArray(flat)) return;
                const tree = buildTree(flat);
                setCompartmentTree(tree);
                if (tree.length > 0 && !selectedCompartment) {
                  setSelectedCompartment(tree[0].id);
                  setTreeOpen((p) => ({ ...p, [tree[0].id]: true }));
                }
              }).finally(() => setTreeLoading(false));
            }}
          >
            <RefreshCw size={13} /> {t('exp.refresh')}
          </button>
        </div>

        {/* KPI Stats Bar */}
        {selectedCompartment && (
          <div
            className="flex items-stretch gap-2 px-3 py-2 border-b overflow-x-auto shrink-0"
            style={{ borderColor: 'var(--bd)', background: 'var(--bg)' }}
          >
            {/* Compartment info */}
            <div className="flex-1 rounded-lg px-3 py-2" style={{ background: 'var(--bg1)', border: '1px solid var(--bd)' }}>
              <div className="flex items-center gap-1.5 text-[.68rem] font-semibold uppercase tracking-wider" style={{ color: 'var(--t3)' }}>
                <FolderTree size={12} /> Compartment
              </div>
              <div className="text-sm font-bold mt-0.5 truncate" style={{ color: 'var(--t1)' }} title={selectedCompName}>
                {selectedCompName}
              </div>
              <div className="text-[.68rem] mt-0.5" style={{ color: 'var(--t4)' }}>
                {selectedRegions.length || t('exp.all')} {t('exp.regions')}
              </div>
            </div>

            {/* Total */}
            <div className="rounded-lg px-3 py-2 text-center shrink-0" style={{ background: 'var(--bg1)', border: '1px solid var(--bd)', minWidth: 80 }}>
              <div className="flex items-center justify-center gap-1 text-[.68rem] font-semibold uppercase tracking-wider" style={{ color: 'var(--t3)' }}>
                <Activity size={12} /> Total
              </div>
              <div className="text-xl font-extrabold mt-0.5" style={{ color: 'var(--t1)' }}>
                {kpiStats.grandTotal}
              </div>
              <div className="text-[.68rem]" style={{ color: 'var(--t4)' }}>
                {countsRefreshing ? (
                  <span className="flex items-center justify-center gap-1">
                    <Loader2 size={10} className="animate-spin" /> {t('exp.updating')}
                  </span>
                ) : t('exp.resources')}
              </div>
            </div>

            {/* Category mini KPIs */}
            {kpiStats.top6.map(({ cat, total }) => {
              const CIcon = CATEGORY_ICONS[cat] || Package;
              return (
                <div key={cat} className="rounded-lg px-3 py-2 text-center shrink-0" style={{ background: 'var(--bg1)', border: '1px solid var(--bd)', minWidth: 75 }}>
                  <div className="flex items-center justify-center text-[.68rem]" style={{ color: 'var(--t3)' }}>
                    <CIcon size={12} />
                  </div>
                  <div className="text-lg font-extrabold" style={{ color: 'var(--t1)' }}>{total}</div>
                  <div className="text-[.62rem]" style={{ color: 'var(--t4)' }}>{CATEGORY_I18N[cat] ? t(CATEGORY_I18N[cat]) : cat}</div>
                </div>
              );
            })}
          </div>
        )}

        {/* Category Tabs */}
        <div
          className="flex items-center gap-0.5 px-2 py-1 border-b overflow-x-auto shrink-0"
          style={{ borderColor: 'var(--bd)', background: 'var(--bg1)' }}
        >
          {CATEGORIES.map((cat) => {
            const CIcon = CATEGORY_ICONS[cat] || Package;
            const isSel = selectedCategory === cat;
            const catTotal = (EXP_GROUPS[cat] || []).reduce((s, t) => s + (counts[t.key] || 0), 0);
            return (
              <button
                key={cat}
                className="flex items-center gap-1 px-2.5 py-1.5 rounded text-xs font-medium transition-colors whitespace-nowrap"
                style={{
                  background: isSel ? 'var(--bg3)' : 'transparent',
                  color: isSel ? 'var(--ac)' : 'var(--t3)',
                  borderBottom: isSel ? '2px solid var(--ac)' : '2px solid transparent',
                }}
                onClick={() => {
                  setSelectedCategory(cat);
                  const types = EXP_GROUPS[cat];
                  if (types.length > 0) setSelectedResourceType(types[0].key);
                }}
              >
                <CIcon size={14} />
                {CATEGORY_I18N[cat] ? t(CATEGORY_I18N[cat]) : cat}
                {catTotal > 0 && (
                  <span
                    className="px-1 py-0 rounded text-[10px] font-bold"
                    style={{ background: isSel ? 'var(--ac)' : 'var(--bg4)', color: isSel ? 'var(--bg)' : 'var(--t3)' }}
                  >
                    {catTotal}
                  </span>
                )}
              </button>
            );
          })}
        </div>

        {/* Resource Type Sub-tabs + Search */}
        <div
          className="flex items-center gap-0.5 px-2 py-1 border-b overflow-x-auto shrink-0"
          style={{ borderColor: 'var(--bd)', background: 'var(--bg)' }}
        >
          {currentTypes.map((t) => {
            const TIcon = ICON_MAP[t.icon] || Package;
            const isSel = selectedResourceType === t.key;
            const c = counts[t.key] || 0;
            return (
              <button
                key={t.key}
                className="flex items-center gap-1 px-2 py-1 rounded text-xs transition-colors whitespace-nowrap"
                style={{
                  background: isSel ? 'var(--bg2)' : 'transparent',
                  color: isSel ? 'var(--t1)' : 'var(--t3)',
                  borderBottom: isSel ? '2px solid var(--ac)' : '2px solid transparent',
                }}
                onClick={() => setSelectedResourceType(t.key)}
              >
                <TIcon size={13} />
                {t.label}
                <span className="text-[10px] opacity-70">({c})</span>
              </button>
            );
          })}
          <div className="ml-auto flex items-center">
            <div className="relative">
              <Search size={13} className="absolute left-2 top-1/2 -translate-y-1/2" style={{ color: 'var(--t4)' }} />
              <input
                type="text"
                placeholder={t('exp.filter')}
                className="pl-7 pr-2 py-1 rounded text-xs border w-36"
                style={{ background: 'var(--bg1)', borderColor: 'var(--bd)', color: 'var(--t1)' }}
                value={searchFilter}
                onChange={(e) => setSearchFilter(e.target.value)}
              />
            </div>
          </div>
        </div>

        {/* Resource Cards */}
        <div className="flex-1 overflow-y-auto p-3">
          {loading && (
            <div className="flex items-center justify-center py-12 gap-2">
              <Loader2 size={20} className="animate-spin" style={{ color: 'var(--ac)' }} />
              <span className="text-sm" style={{ color: 'var(--t3)' }}>{t('exp.loadingResources')}</span>
            </div>
          )}

          {!loading && filteredResources !== null && filteredResources.length === 0 && (
            <div className="flex flex-col items-center justify-center py-16 gap-3">
              <Info size={32} style={{ color: 'var(--t4)' }} />
              <span className="text-sm" style={{ color: 'var(--t3)' }}>
                {t('exp.noResources')}
              </span>
              {searchFilter && (
                <span className="text-xs" style={{ color: 'var(--t4)' }}>
                  {t('exp.clearFilter')}
                </span>
              )}
            </div>
          )}

          {!loading && filteredResources && filteredResources.length > 0 && (
            <div className="grid gap-3" style={{ gridTemplateColumns: 'repeat(auto-fill, minmax(360px, 1fr))' }}>
              {filteredResources.map((item, idx) => (
                <ResourceCard
                  key={item.id || item.name || idx}
                  item={item}
                  resourceType={selectedResourceType}
                  configId={selectedConfig}
                  actionPending={actionPending[item.id || item.name] || false}
                  onAction={handleAction}
                  regions={selectedRegions}
                  onReload={() => loadResources()}
                />
              ))}
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

/* ══════════════════════════════════════════════════════════════════════════════
   Resource Card Component
   ══════════════════════════════════════════════════════════════════════════════ */

function ResourceCard({
  item, resourceType, configId, actionPending, onAction, regions, onReload,
}: {
  item: ResourceItem;
  resourceType: string;
  configId: string;
  actionPending: boolean;
  onAction: (item: ResourceItem, action: string) => void;
  regions: string[];
  onReload: () => void;
}) {
  const { t } = useI18n();
  const name = item.display_name || item.name || item.id || '—';
  const state = item.lifecycle_state;
  const startable = STARTABLE[resourceType];

  let canStart = false;
  let canStop = false;
  if (startable && state) {
    const upper = state.toUpperCase();
    canStart = startable.stopped.includes(upper);
    canStop = startable.running.includes(upper);
  }

  /* Special card body */
  const specialBody = (() => {
    switch (resourceType) {
      case 'instances': return <InstanceCard r={item} />;
      case 'databases': return <AdbCard r={item} configId={configId} regions={regions} onReload={onReload} />;
      case 'db_systems': return <DbSystemCard r={item} />;
      case 'mysql_db_systems': return <MysqlCard r={item} />;
      case 'container_instances': return <ContainerCard r={item} />;
      case 'vcns': return <VcnCard r={item} />;
      case 'subnets': return <SubnetCard r={item} />;
      case 'buckets': return <BucketCard r={item} />;
      case 'load_balancers': return <LoadBalancerCard r={item} />;
      case 'security_lists': return <SecurityListCard r={item} configId={configId} regions={regions} onReload={onReload} />;
      case 'nsgs': return <NsgCard r={item} configId={configId} regions={regions} onReload={onReload} />;
      case 'route_tables': return <RouteTableCard r={item} />;
      default: return null;
    }
  })();

  /* Generic body: show all keys except common ones */
  const SKIP_KEYS = new Set(['id', 'display_name', 'name', 'lifecycle_state', 'rules', 'listeners',
    'backend_sets', 'routes', 'ingress_count', 'egress_count', 'rule_count', 'route_count',
    'cidr_blocks', 'dns_label', 'vcn_domain_name', 'approximate_size', 'approximate_count',
    'public_access_type', 'versioning', 'storage_tier', 'namespace', 'shape_name', 'ip_addresses',
    'is_private', 'cidr_block', 'availability_domain', 'prohibit_public_ip_on_vnic', 'route_table_id',
    'security_list_ids', 'subnet_domain_name', 'vcn_id', 'default_route_table_id', 'default_security_list_id',
    'db_name', 'whitelisted_ips', 'cpu_core_count', 'data_storage_size_in_tbs', 'is_free_tier',
    'database_edition', 'node_count', 'mysql_version', 'is_highly_available', 'container_count',
    'shape', 'region', 'time_created', 'application',
  ]);

  const genericEntries = specialBody ? [] : Object.entries(item).filter(
    ([k]) => !SKIP_KEYS.has(k)
  );

  return (
    <div
      className="rounded-lg border p-3 transition-colors hover:border-opacity-80"
      style={{
        background: 'var(--bg1)',
        borderColor: 'var(--bd)',
        boxShadow: 'var(--sh1)',
      }}
    >
      {/* Header */}
      <div className="flex items-start justify-between gap-2 mb-2">
        <div className="min-w-0">
          <h4 className="text-sm font-semibold truncate" style={{ color: 'var(--t1)' }} title={name}>
            {name}
          </h4>
          {item.id && (
            <span className="text-[10px] font-mono block truncate" style={{ color: 'var(--t4)' }} title={item.id}>
              {truncId(item.id)}
            </span>
          )}
        </div>
        <div className="flex items-center gap-1.5 shrink-0">
          {state && <StateBadge state={state} />}
          {actionPending && (
            <Loader2 size={14} className="animate-spin" style={{ color: 'var(--ac)' }} />
          )}
        </div>
      </div>

      {/* Body */}
      <div className="space-y-0.5">
        {specialBody || (
          <>
            {genericEntries.map(([k, v]) => (
              <KV
                key={k}
                label={k.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase())}
                value={
                  typeof v === 'boolean' ? (v ? t('exp.yes') : t('exp.no')) :
                  Array.isArray(v) ? v.join(', ') :
                  typeof v === 'object' && v !== null ? JSON.stringify(v) :
                  String(v ?? '—')
                }
                mono={typeof v === 'string' && (v.startsWith('ocid') || v.includes('.'))}
              />
            ))}
          </>
        )}
      </div>

      {/* Start/Stop Buttons */}
      {(canStart || canStop) && !actionPending && (
        <div className="flex gap-2 mt-3 pt-2 border-t" style={{ borderColor: 'var(--bg3)' }}>
          {canStart && (
            <button
              className="flex items-center gap-1 px-3 py-1 rounded text-xs font-medium transition-colors"
              style={{ background: 'var(--gnl)', color: 'var(--gn)' }}
              onClick={() => onAction(item, 'START')}
            >
              <Play size={12} /> {t('exp.startAction')}
            </button>
          )}
          {canStop && (
            <button
              className="flex items-center gap-1 px-3 py-1 rounded text-xs font-medium transition-colors"
              style={{ background: 'var(--rdl)', color: 'var(--rd)' }}
              onClick={() => onAction(item, 'STOP')}
            >
              <Square size={12} /> {t('exp.stopAction')}
            </button>
          )}
        </div>
      )}
      {actionPending && (
        <div className="flex items-center gap-1 mt-3 pt-2 border-t text-xs" style={{ borderColor: 'var(--bg3)', color: 'var(--ac)' }}>
          <Loader2 size={12} className="animate-spin" />
          {t('exp.updatingState')}
        </div>
      )}
    </div>
  );
}
