import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import rehypeHighlight from 'rehype-highlight';
import {
  Code, Send, History, ChevronRight, ChevronDown, Trash2, RefreshCw,
  Play, Rocket, Flame, Ban, Download, Copy, Save, FileCode, Terminal,
  Settings, Loader2, Plus, Wrench, RotateCcw, X, FolderOpen, Menu,
  Search, Check, File, Layers,
} from 'lucide-react';
import { useAppStore } from '@/stores/app';
import { useI18n } from '@/i18n';
import {
  terraformApi,
  type Workspace, type WsStatus, type ChatMessage, type ChatSession,
} from '@/api/endpoints/terraform';

/* ═══════════════════════════════════════════════════════════════════════
   Constants & helpers
   ═══════════════════════════════════════════════════════════════════════ */

const RUNNING: WsStatus[] = ['planning', 'applying', 'destroying'];

const BADGE: Record<string, { cls: string; label: string }> = {
  draft:      { cls: 'bg-[var(--bg3)] text-[var(--t3)]', label: 'Draft' },
  planning:   { cls: 'bg-[var(--ppl)] text-[var(--pp)]', label: 'Planning...' },
  planned:    { cls: 'bg-[var(--gnl)] text-[var(--gn)]', label: 'Plan OK' },
  applying:   { cls: 'bg-[var(--bll)] text-[var(--bl)]', label: 'Applying...' },
  applied:    { cls: 'bg-[var(--gnl)] text-[var(--gn)]', label: 'Applied' },
  destroying: { cls: 'bg-[var(--rdl)] text-[var(--rd)]', label: 'Destroying...' },
  destroyed:  { cls: 'bg-[var(--bg3)] text-[var(--t4)]', label: 'Destroyed' },
  failed:     { cls: 'bg-[var(--rdl)] text-[var(--rd)]', label: 'Failed' },
};

interface TfFile {
  name: string;
  content: string;
  size: number;
}

const ALLOWED_MODELS = [
  { id: 'openai.gpt-4.1', label: 'GPT-4.1', provider: 'OpenAI' },
  { id: 'openai.o3', label: 'o3', provider: 'OpenAI' },
  { id: 'openai.o4-mini', label: 'o4-mini', provider: 'OpenAI' },
  { id: 'openai.gpt-5.1', label: 'GPT-5.1', provider: 'OpenAI' },
  { id: 'openai.gpt-5.2', label: 'GPT-5.2', provider: 'OpenAI' },
  { id: 'google.gemini-2.5-pro', label: 'Gemini 2.5 Pro', provider: 'Google' },
  { id: 'google.gemini-2.5-flash', label: 'Gemini 2.5 Flash', provider: 'Google' },
];

/* ── file parsing ── */

function parseFiles(code: string | undefined | null): TfFile[] {
  if (!code) return [];
  const parts = code.split(/^\/\/\s*filename:\s*/m).filter(Boolean);
  return parts.map((p) => {
    const nl = p.indexOf('\n');
    const name = nl > 0 ? p.slice(0, nl).trim() : 'main.tf';
    const content = nl > 0 ? p.slice(nl + 1) : p;
    return { name, content, size: new TextEncoder().encode(content).length };
  });
}

function joinFiles(files: TfFile[]): string {
  return files.map((f) => `// filename: ${f.name}\n${f.content}`).join('\n\n');
}

function extractFilesFromResponse(text: string): TfFile[] | null {
  // Extract ONLY hcl/terraform tagged code blocks (skip plain/untagged blocks)
  const rawBlocks: string[] = [];
  const re = /```(?:hcl|terraform|tf)\s*\n([\s\S]*?)```/g;
  let m: RegExpExecArray | null;
  while ((m = re.exec(text)) !== null) rawBlocks.push(m[1]);
  if (rawBlocks.length === 0) {
    // Fallback: try untagged blocks that contain // filename:
    const re2 = /```\s*\n([\s\S]*?)```/g;
    while ((m = re2.exec(text)) !== null) {
      if (m[1].includes('// filename:')) rawBlocks.push(m[1]);
    }
    if (rawBlocks.length === 0) return null;
  }

  const allCode = rawBlocks.join('\n\n');

  // If contains // filename: markers, split by them
  if (allCode.includes('// filename:')) {
    return parseFiles(allCode);
  }

  // No filename markers — single file
  return [{ name: 'main.tf', content: allCode.trim(), size: new TextEncoder().encode(allCode).length }];
}

/** Merge new files from model response with existing files (keep unchanged files) */
function mergeFiles(existing: TfFile[], incoming: TfFile[]): TfFile[] {
  if (!existing.length) return incoming;
  const newMap: Record<string, string> = {};
  for (const f of incoming) newMap[f.name] = f.content;
  const merged: TfFile[] = [];
  const used = new Set<string>();
  // Update existing files or keep them
  for (const f of existing) {
    if (newMap[f.name] != null) {
      merged.push({ name: f.name, content: newMap[f.name], size: new TextEncoder().encode(newMap[f.name]).length });
      used.add(f.name);
    } else {
      merged.push(f);
    }
  }
  // Add new files not in existing
  for (const [name, content] of Object.entries(newMap)) {
    if (!used.has(name)) {
      merged.push({ name, content, size: new TextEncoder().encode(content).length });
    }
  }
  return merged;
}

/* ── plan parsing ── */

interface PlanChange {
  action: '+' | '~' | '-';
  type: string;
  name: string;
}

function parsePlan(output: string | undefined): PlanChange[] {
  if (!output) return [];
  const changes: PlanChange[] = [];
  const lines = output.split('\n');
  for (const line of lines) {
    const trimmed = line.trim();
    let action: '+' | '~' | '-' | null = null;
    if (trimmed.startsWith('# ') && trimmed.includes(' will be ')) {
      const resource = trimmed.slice(2).split(' will be ')[0].trim();
      const verb = trimmed.split(' will be ')[1]?.toLowerCase() || '';
      if (verb.includes('created')) action = '+';
      else if (verb.includes('updated') || verb.includes('changed')) action = '~';
      else if (verb.includes('destroyed')) action = '-';
      if (action) {
        const dot = resource.lastIndexOf('.');
        changes.push({
          action,
          type: dot > 0 ? resource.slice(0, dot) : resource,
          name: dot > 0 ? resource.slice(dot + 1) : resource,
        });
      }
    }
  }
  return changes;
}

/* ── terminal coloring ── */

function colorTerminalLine(line: string): { color: string; bold: boolean } {
  const t = line.trimStart();
  if (t.startsWith('+') || t.startsWith('# ') && t.includes('created'))
    return { color: 'var(--gn)', bold: false };
  if (t.startsWith('~') || t.startsWith('# ') && t.includes('updated'))
    return { color: 'var(--yl)', bold: false };
  if (t.startsWith('-') || t.startsWith('# ') && t.includes('destroyed'))
    return { color: 'var(--rd)', bold: false };
  if (t.startsWith('Plan:'))
    return { color: 'var(--pp)', bold: true };
  if (t.startsWith('Apply complete'))
    return { color: 'var(--gn)', bold: true };
  if (t.startsWith('Error'))
    return { color: 'var(--rd)', bold: true };
  return { color: 'var(--t3)', bold: false };
}

/* ── date helpers ── */

function dateLabel(dt: string | undefined): string {
  const t = useI18n.getState().t;
  if (!dt) return t('chat.other');
  const slice = dt.slice(0, 10);
  const today = new Date().toISOString().slice(0, 10);
  const yesterday = new Date(Date.now() - 86400000).toISOString().slice(0, 10);
  if (slice === today) return t('chat.today');
  if (slice === yesterday) return t('chat.yesterday');
  const d = new Date(slice + 'T00:00:00');
  const diff = Math.floor((Date.now() - d.getTime()) / 86400000);
  if (diff < 7) return d.toLocaleDateString('pt-BR', { weekday: 'long' });
  return d.toLocaleDateString('pt-BR', { day: '2-digit', month: 'short', year: 'numeric' });
}

/* ── CRC32 + ZIP ── */

function crc32(buf: Uint8Array): number {
  let c = 0xFFFFFFFF;
  const table: number[] = [];
  for (let n = 0; n < 256; n++) {
    let v = n;
    for (let k = 0; k < 8; k++) v = v & 1 ? 0xEDB88320 ^ (v >>> 1) : v >>> 1;
    table[n] = v;
  }
  for (let i = 0; i < buf.length; i++) c = table[(c ^ buf[i]) & 0xFF] ^ (c >>> 8);
  return (c ^ 0xFFFFFFFF) >>> 0;
}

function buildZip(files: { name: string; data: Uint8Array }[]): Uint8Array {
  const enc = new TextEncoder();
  const entries: { name: Uint8Array; data: Uint8Array; crc: number; offset: number }[] = [];
  const parts: Uint8Array[] = [];
  let offset = 0;

  for (const f of files) {
    const nameBytes = enc.encode(f.name);
    const crc = crc32(f.data);
    // local file header: 30 bytes + name
    const lh = new Uint8Array(30 + nameBytes.length);
    const lv = new DataView(lh.buffer);
    lv.setUint32(0, 0x04034b50, true);   // sig
    lv.setUint16(4, 20, true);            // version
    lv.setUint16(6, 0, true);             // flags
    lv.setUint16(8, 0, true);             // compression (store)
    lv.setUint16(10, 0, true);            // mod time
    lv.setUint16(12, 0, true);            // mod date
    lv.setUint32(14, crc, true);
    lv.setUint32(18, f.data.length, true);
    lv.setUint32(22, f.data.length, true);
    lv.setUint16(26, nameBytes.length, true);
    lv.setUint16(28, 0, true);
    lh.set(nameBytes, 30);

    entries.push({ name: nameBytes, data: f.data, crc, offset });
    parts.push(lh, f.data);
    offset += lh.length + f.data.length;
  }

  const cdStart = offset;
  const cdParts: Uint8Array[] = [];
  for (const e of entries) {
    const cd = new Uint8Array(46 + e.name.length);
    const cv = new DataView(cd.buffer);
    cv.setUint32(0, 0x02014b50, true);
    cv.setUint16(4, 20, true);
    cv.setUint16(6, 20, true);
    cv.setUint16(8, 0, true);
    cv.setUint16(10, 0, true);
    cv.setUint16(12, 0, true);
    cv.setUint16(14, 0, true);
    cv.setUint32(16, e.crc, true);
    cv.setUint32(20, e.data.length, true);
    cv.setUint32(24, e.data.length, true);
    cv.setUint16(28, e.name.length, true);
    cv.setUint16(30, 0, true);
    cv.setUint16(32, 0, true);
    cv.setUint16(34, 0, true);
    cv.setUint16(36, 0, true);
    cv.setUint32(38, 0x20, true);
    cv.setUint32(42, e.offset, true);
    cd.set(e.name, 46);
    cdParts.push(cd);
  }

  const cdBuf = concatBytes(cdParts);
  const eocd = new Uint8Array(22);
  const ev = new DataView(eocd.buffer);
  ev.setUint32(0, 0x06054b50, true);
  ev.setUint16(4, 0, true);
  ev.setUint16(6, 0, true);
  ev.setUint16(8, entries.length, true);
  ev.setUint16(10, entries.length, true);
  ev.setUint32(12, cdBuf.length, true);
  ev.setUint32(16, cdStart, true);
  ev.setUint16(20, 0, true);

  return concatBytes([...parts, cdBuf, eocd]);
}

function concatBytes(arrays: Uint8Array[]): Uint8Array {
  const total = arrays.reduce((s, a) => s + a.length, 0);
  const r = new Uint8Array(total);
  let off = 0;
  for (const a of arrays) { r.set(a, off); off += a.length; }
  return r;
}

function downloadZip(files: TfFile[], wsName: string) {
  const enc = new TextEncoder();
  const zipFiles = files.map((f) => ({ name: f.name, data: enc.encode(f.content) }));
  // auto-generate terraform.tfvars stub
  const tfvarsContent = `# Auto-generated terraform.tfvars\n# Preencha as variaveis abaixo\n`;
  if (!files.some((f) => f.name === 'terraform.tfvars')) {
    zipFiles.push({ name: 'terraform.tfvars', data: enc.encode(tfvarsContent) });
  }
  const raw = buildZip(zipFiles);
  const blob = new Blob([new Uint8Array(raw)], { type: 'application/zip' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = `${wsName || 'terraform'}.zip`;
  a.click();
  URL.revokeObjectURL(url);
}

/* ── compartment flattening ── */

interface CompartmentNode {
  id: string;
  name: string;
  children?: CompartmentNode[];
}

interface FlatCompartment {
  id: string;
  name: string;
  depth: number;
}

function flattenCompartments(nodes: CompartmentNode[], depth = 0): FlatCompartment[] {
  const result: FlatCompartment[] = [];
  for (const n of nodes) {
    result.push({ id: n.id, name: n.name, depth });
    if (n.children) result.push(...flattenCompartments(n.children, depth + 1));
  }
  return result;
}

/* ═══════════════════════════════════════════════════════════════════════
   StatusBadge
   ═══════════════════════════════════════════════════════════════════════ */

function StatusBadge({ status }: { status: WsStatus }) {
  const b = BADGE[status] || BADGE.draft;
  return (
    <span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-[.68rem] font-semibold whitespace-nowrap ${b.cls}`}>
      {RUNNING.includes(status) && (
        <span className="inline-block w-2 h-2 rounded-full mr-1.5 animate-pulse"
          style={{ background: 'currentColor' }} />
      )}
      {b.label}
    </span>
  );
}

/* ═══════════════════════════════════════════════════════════════════════
   Modal
   ═══════════════════════════════════════════════════════════════════════ */

function Modal({ children, onClose }: { children: React.ReactNode; onClose: () => void }) {
  return (
    <div className="fixed inset-0 z-50 flex items-center justify-center"
         style={{ background: 'rgba(0,0,0,.55)' }} onClick={onClose}>
      <div className="w-full max-w-sm rounded-xl p-5 animate-[fadeIn_.2s_ease]"
           style={{ background: 'var(--bg1)', border: '1px solid var(--bd)', boxShadow: 'var(--sh3)' }}
           onClick={(e) => e.stopPropagation()}>
        {children}
      </div>
    </div>
  );
}

/* ═══════════════════════════════════════════════════════════════════════
   MAIN COMPONENT
   ═══════════════════════════════════════════════════════════════════════ */

export default function TerraformPage() {
  const { t } = useI18n();
  const { ociCfg, genaiCfg } = useAppStore();

  /* ── Chat state ── */
  const [messages, setMessages] = useState<ChatMessage[]>([]);
  const [sessionId, setSessionId] = useState<string | null>(null);
  const [historyOpen, setHistoryOpen] = useState(false);
  const [sessions, setSessions] = useState<ChatSession[]>([]);
  const [chatInput, setChatInput] = useState('');
  const [sending, setSending] = useState(false);
  const chatEndRef = useRef<HTMLDivElement>(null);
  const inputRef = useRef<HTMLTextAreaElement>(null);

  /* ── Model state ── */
  const [model, setModel] = useState('openai.gpt-4.1');
  const [ociConfig, setOciConfig] = useState('');
  const [region, setRegion] = useState('');
  const [compartment, setCompartment] = useState('');
  const [compartments, setCompartments] = useState<FlatCompartment[]>([]);
  const [modelDropdownOpen, setModelDropdownOpen] = useState(false);
  const [modelSearch, setModelSearch] = useState('');
  const [menuOpen, setMenuOpen] = useState(false);
  const [tfTemperature, setTfTemperature] = useState(0.3);

  /* ── Workspace state ── */
  const [wsId, setWsId] = useState<string | null>(null);
  const [wsStatus, setWsStatus] = useState<WsStatus>('draft');
  const [wsName, setWsName] = useState('');
  const [files, setFiles] = useState<TfFile[]>([]);
  const [planOutput, setPlanOutput] = useState('');
  const [applyOutput, setApplyOutput] = useState('');
  const [destroyOutput, setDestroyOutput] = useState('');
  const [wsError, setWsError] = useState('');

  /* ── UI state ── */
  const [editingFileIdx, setEditingFileIdx] = useState<number | null>(null);
  const [bottomTab, setBottomTab] = useState<'files' | 'plan' | 'resources'>('files');
  const [running, setRunning] = useState(false);
  const [terminalView, setTerminalView] = useState<'plan' | 'apply' | 'destroy'>('plan');
  const terminalRef = useRef<HTMLPreElement>(null);

  /* ── Resources ── */
  const [resources, setResources] = useState<Record<string, any[]>>({});
  const [resourcesLoading, setResourcesLoading] = useState(false);

  /* ── Confirm modals ── */
  const [confirmDestroy, setConfirmDestroy] = useState(false);
  const [destroyInput, setDestroyInput] = useState('');

  /* ── Bottom panel resize ── */
  const [bottomHeight, setBottomHeight] = useState(280);
  const resizingRef = useRef(false);
  const containerRef = useRef<HTMLDivElement>(null);

  /* poll ref to cancel */
  const pollCancelRef = useRef(false);

  /* ── Init: OCI config default ── */
  useEffect(() => {
    if (!ociConfig && ociCfg.length > 0) {
      setOciConfig(ociCfg[0].id);
      setRegion(ociCfg[0].region || '');
    }
  }, [ociCfg, ociConfig]);

  /* ── Load compartments when OCI config changes ── */
  useEffect(() => {
    if (!ociConfig) return;
    const cfg = ociCfg.find((c) => c.id === ociConfig);
    if (cfg) {
      setRegion(cfg.region || '');
      if (cfg.compartment_id) {
        terraformApi.getCompartmentTree(ociConfig).then((tree) => {
          setCompartments(flattenCompartments(tree));
          if (!compartment && cfg.compartment_id) setCompartment(cfg.compartment_id);
        }).catch(() => {
          setCompartments([]);
        });
      }
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [ociConfig, ociCfg]);

  /* ── URL param: ?ws=WID ── */
  useEffect(() => {
    const params = new URLSearchParams(window.location.search);
    const wsParam = params.get('ws');
    if (wsParam) loadWorkspace(wsParam);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  /* ── Auto-scroll chat ── */
  useEffect(() => {
    chatEndRef.current?.scrollIntoView({ behavior: 'smooth' });
  }, [messages]);

  /* ── Auto-scroll terminal ── */
  useEffect(() => {
    if (terminalRef.current) {
      terminalRef.current.scrollTop = terminalRef.current.scrollHeight;
    }
  }, [planOutput, applyOutput, destroyOutput]);

  /* ── Load sessions for history ── */
  const loadSessions = useCallback(async () => {
    try {
      const s = await terraformApi.listSessions('terraform', 50);
      setSessions(s);
    } catch { setSessions([]); }
  }, []);

  useEffect(() => { loadSessions(); }, [loadSessions]);

  /* ── Load workspace ── */
  const loadWorkspace = useCallback(async (wid: string) => {
    try {
      const ws = await terraformApi.getWorkspace(wid);
      setWsId(ws.id);
      setWsName(ws.name);
      setWsStatus(ws.status);
      setPlanOutput(ws.plan_output || '');
      setApplyOutput(ws.apply_output || '');
      setDestroyOutput(ws.destroy_output || '');
      setWsError(ws.error || '');
      setFiles(parseFiles(ws.tf_code));
      setSessionId(ws.session_id);
      if (ws.oci_config_id) setOciConfig(ws.oci_config_id);
      if (ws.compartment_id) setCompartment(ws.compartment_id);

      // Load chat messages
      if (ws.session_id) {
        try {
          const r = await terraformApi.loadSession(ws.session_id);
          setMessages(r.messages || []);
        } catch { /* no messages */ }
      }

      // Auto-select terminal view
      if (ws.destroy_output) setTerminalView('destroy');
      else if (ws.apply_output) setTerminalView('apply');
      else if (ws.plan_output) setTerminalView('plan');

      // If running, start polling
      if (RUNNING.includes(ws.status)) {
        setRunning(true);
        pollWorkspaceStatus(ws.id);
      }
    } catch (e: unknown) {
      alert('Erro ao carregar workspace: ' + (e as Error).message);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  /* ── Load session (chat history + associated workspace) ── */
  const loadSession = useCallback(async (sid: string) => {
    try {
      const r = await terraformApi.loadSession(sid);
      setMessages(r.messages || []);
      setSessionId(sid);
      setHistoryOpen(false);

      // Try to find and load the workspace associated with this session
      try {
        const workspaces = await terraformApi.listWorkspaces();
        const ws = workspaces.find((w) => w.session_id === sid);
        if (ws) {
          await loadWorkspace(ws.id);
        } else {
          // No workspace — reset workspace state but keep messages
          setWsId(null);
          setWsName('');
          setWsStatus('draft');
          setFiles([]);
          setPlanOutput('');
          setApplyOutput('');
          setDestroyOutput('');
          setWsError('');
        }
      } catch { /* no workspace */ }
    } catch (e: unknown) {
      alert('Erro: ' + (e as Error).message);
    }
  }, [loadWorkspace]);

  /* ── Delete session ── */
  const deleteSession = useCallback(async (sid: string) => {
    try {
      await terraformApi.deleteSession(sid);
      setSessions((prev) => prev.filter((s) => s.id !== sid));
      if (sessionId === sid) {
        setSessionId(null);
        setMessages([]);
      }
    } catch { /* noop */ }
  }, [sessionId]);

  /* ── New chat ── */
  const newChat = useCallback(() => {
    setSessionId(null);
    setMessages([]);
    setWsId(null);
    setWsName('');
    setWsStatus('draft');
    setFiles([]);
    setPlanOutput('');
    setApplyOutput('');
    setDestroyOutput('');
    setWsError('');
    setEditingFileIdx(null);
  }, []);

  /* ── Model resolution ── */
  const resolveModel = useCallback(() => {
    if (model.startsWith('cfg:')) {
      const cfgId = model.slice(4);
      return { genai_config_id: cfgId };
    }
    return { model_id: model, oci_config_id: ociConfig, genai_region: region };
  }, [model, ociConfig, region]);

  /* ── Send chat message ── */
  const sendMessage = useCallback(async (overrideMsg?: string) => {
    const text = (overrideMsg || chatInput).trim();
    if (!text || sending) return;
    setSending(true);
    setChatInput('');

    // Build context (aligned with legacy frontend)
    let fullMessage = text;
    if (files.length > 0) {
      const filesCtx = files.map((f) => `// filename: ${f.name}\n${f.content}`).join('\n\n');
      fullMessage += '\n\n---\nARQUIVOS TERRAFORM ATUAIS NO WORKSPACE:\n```hcl\n' + filesCtx + '\n```\n\n⚠️ REGRA OBRIGATÓRIA: Gere APENAS os arquivos que precisam de correção. NÃO regenere arquivos sem erro. Se o erro está em um arquivo específico, retorne SOMENTE esse arquivo com `// filename:`. Arquivos não mencionados na sua resposta serão MANTIDOS como estão.';
    }
    if (planOutput) {
      fullMessage += '\n\n---\nÚLTIMO OUTPUT DO TERRAFORM PLAN:\n```\n' + planOutput.slice(-3000) + '\n```';
    }
    if (applyOutput) {
      fullMessage += '\n\n---\nÚLTIMO OUTPUT DO TERRAFORM APPLY:\n```\n' + applyOutput.slice(-3000) + '\n```';
    }

    // Add user message to UI immediately
    const userMsg: ChatMessage = {
      id: 'tmp-' + Date.now(),
      role: 'user',
      content: text,
      created_at: new Date().toISOString(),
    };
    setMessages((prev) => [...prev, userMsg]);

    try {
      const modelCfg = resolveModel();
      const res = await terraformApi.chat({
        message: fullMessage,
        session_id: sessionId || undefined,
        ...modelCfg,
        compartment_id: compartment || undefined,
        temperature: tfTemperature,
        use_tools: true,
      });

      if (!sessionId) setSessionId(res.session_id);

      // Poll for response
      const mid = res.message_id;
      const assistantMsg: ChatMessage = {
        id: mid,
        role: 'assistant',
        content: '',
        created_at: new Date().toISOString(),
      };
      setMessages((prev) => [...prev, assistantMsg]);

      for (let i = 0; i < 600; i++) {
        await new Promise((r) => setTimeout(r, 2000));
        try {
          const st = await terraformApi.pollMessageStatus(mid);
          setMessages((prev) =>
            prev.map((m) => m.id === mid ? { ...m, content: st.content || '' } : m)
          );
          if (st.status === 'done' || st.status === 'failed') {
            // Extract files from response and merge with existing
            if (st.status === 'done' && st.content) {
              const extracted = extractFilesFromResponse(st.content);
              if (extracted && extracted.length > 0) {
                setFiles((prev) => prev.length > 0 ? mergeFiles(prev, extracted) : extracted);
                setBottomTab('files');
              }
            }
            break;
          }
        } catch { /* keep polling */ }
      }

      loadSessions();
    } catch (e: unknown) {
      setMessages((prev) => [
        ...prev,
        { id: 'err-' + Date.now(), role: 'assistant', content: `Erro: ${(e as Error).message}`, created_at: new Date().toISOString() },
      ]);
    }
    setSending(false);
  }, [chatInput, sending, sessionId, files, planOutput, applyOutput, wsError, resolveModel, compartment, loadSessions]);

  /* ── Poll workspace status ── */
  const pollWorkspaceStatus = useCallback(async (wid: string) => {
    pollCancelRef.current = false;
    setRunning(true);
    for (let i = 0; i < 1800; i++) {
      if (pollCancelRef.current) break;
      await new Promise((r) => setTimeout(r, 2000));
      try {
        const r = await terraformApi.status(wid);
        setWsStatus(r.status);
        if (r.plan_output) setPlanOutput(r.plan_output);
        if (r.apply_output) setApplyOutput(r.apply_output);
        if (r.destroy_output) setDestroyOutput(r.destroy_output);
        if (r.error) setWsError(r.error);
        if (!RUNNING.includes(r.status)) {
          setRunning(false);
          return;
        }
      } catch { /* keep polling */ }
    }
    setRunning(false);
  }, []);

  /* ── Save & Plan ── */
  const doPlan = useCallback(async () => {
    if (files.length === 0) { alert('Nenhum arquivo para planejar'); return; }
    const code = joinFiles(files);
    try {
      let wid = wsId;
      if (!wid) {
        const ws = await terraformApi.createWorkspace({
          session_id: sessionId || undefined,
          oci_config_id: ociConfig,
          compartment_id: compartment || undefined,
          name: wsName || files[0]?.name || 'workspace',
          tf_code: code,
        });
        wid = ws.id;
        setWsId(ws.id);
        setWsName(ws.name);
        setWsStatus(ws.status);
      } else {
        await terraformApi.updateCode(wid, code);
      }
      await terraformApi.plan(wid);
      setWsStatus('planning');
      setTerminalView('plan');
      setPlanOutput('');
      setWsError('');
      pollWorkspaceStatus(wid);
    } catch (e: unknown) {
      alert('Erro: ' + (e as Error).message);
    }
  }, [files, wsId, sessionId, ociConfig, compartment, wsName, pollWorkspaceStatus]);

  /* ── Apply ── */
  const doApply = useCallback(async () => {
    if (!wsId) return;
    try {
      await terraformApi.apply(wsId);
      setWsStatus('applying');
      setTerminalView('apply');
      setApplyOutput('');
      pollWorkspaceStatus(wsId);
    } catch (e: unknown) {
      alert('Erro: ' + (e as Error).message);
    }
  }, [wsId, pollWorkspaceStatus]);

  /* ── Destroy ── */
  const doDestroy = useCallback(async () => {
    if (!wsId || destroyInput !== 'DESTROY') return;
    setConfirmDestroy(false);
    setDestroyInput('');
    try {
      await terraformApi.destroy(wsId);
      setWsStatus('destroying');
      setTerminalView('destroy');
      setDestroyOutput('');
      pollWorkspaceStatus(wsId);
    } catch (e: unknown) {
      alert('Erro: ' + (e as Error).message);
    }
  }, [wsId, destroyInput, pollWorkspaceStatus]);

  /* ── Cancel ── */
  const doCancel = useCallback(async () => {
    if (!wsId) return;
    pollCancelRef.current = true;
    try { await terraformApi.cancel(wsId); } catch { /* noop */ }
    setRunning(false);
    if (wsId) {
      try {
        const r = await terraformApi.status(wsId);
        setWsStatus(r.status);
      } catch { /* noop */ }
    }
  }, [wsId]);

  /* ── Auto-fix ── */
  const doAutoFix = useCallback(() => {
    if (!wsError && !planOutput) return;
    const errLines = planOutput.split('\n').filter((l) =>
      /^Error:|^\s+on\s+\S+\.tf\s+line|^\s+\d+:|has not been declared|is not expected|Missing required|Unsupported|Invalid|Duplicate|undeclared/i.test(l)
    );
    const errCtx = errLines.length ? errLines.join('\n') : (wsError || planOutput.slice(-2000));
    const fixPrompt = `O terraform plan falhou com os seguintes erros. Corrija SOMENTE os arquivos afetados.\n\nERROS DO PLAN:\n\`\`\`\n${errCtx}\n\`\`\``;
    sendMessage(fixPrompt);
  }, [wsError, planOutput, sendMessage]);

  /* ── Rollback (re-plan with previous code) ── */
  const doRollback = useCallback(() => {
    if (!wsId) return;
    // Reload workspace to get original code
    loadWorkspace(wsId);
  }, [wsId, loadWorkspace]);

  /* ── Load resources ── */
  const loadResources = useCallback(async () => {
    if (!ociConfig || !compartment) {
      alert('Selecione OCI Config e Compartment');
      return;
    }
    setResourcesLoading(true);
    try {
      const r = await terraformApi.loadResources(ociConfig, compartment, region || undefined);
      setResources(r);
    } catch (e: unknown) {
      alert('Erro: ' + (e as Error).message);
      setResources({});
    }
    setResourcesLoading(false);
  }, [ociConfig, compartment, region]);

  /* ── File editing ── */
  const updateFileContent = useCallback((idx: number, content: string) => {
    setFiles((prev) => prev.map((f, i) =>
      i === idx ? { ...f, content, size: new TextEncoder().encode(content).length } : f
    ));
  }, []);

  const copyFile = useCallback((content: string) => {
    navigator.clipboard.writeText(content);
  }, []);

  const downloadFile = useCallback((file: TfFile) => {
    const blob = new Blob([file.content], { type: 'text/plain' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = file.name;
    a.click();
    URL.revokeObjectURL(url);
  }, []);

  /* ── Bottom panel resize ── */
  const onResizeStart = useCallback((e: React.MouseEvent) => {
    e.preventDefault();
    resizingRef.current = true;
    const startY = e.clientY;
    const startH = bottomHeight;
    const onMove = (ev: MouseEvent) => {
      if (!resizingRef.current) return;
      const diff = startY - ev.clientY;
      const newH = Math.max(150, Math.min(startH + diff, window.innerHeight * 0.6));
      setBottomHeight(newH);
    };
    const onUp = () => {
      resizingRef.current = false;
      window.removeEventListener('mousemove', onMove);
      window.removeEventListener('mouseup', onUp);
    };
    window.addEventListener('mousemove', onMove);
    window.addEventListener('mouseup', onUp);
  }, [bottomHeight]);

  /* ── Session groups for history sidebar ── */
  const sessionGroups = useMemo(() => {
    const groups: { label: string; items: ChatSession[] }[] = [];
    const gMap: Record<string, number> = {};
    for (const s of sessions) {
      const dt = (s.updated_at || s.created_at || '').slice(0, 10);
      const label = dateLabel(dt);
      if (gMap[label] === undefined) { gMap[label] = groups.length; groups.push({ label, items: [] }); }
      groups[gMap[label]].items.push(s);
    }
    return groups;
  }, [sessions]);

  /* ── Model options (allowed + GenAI configs) ── */
  const modelOptions = useMemo(() => {
    const opts: { id: string; label: string; group: string }[] = [];
    for (const m of ALLOWED_MODELS) {
      opts.push({ id: m.id, label: m.label, group: m.provider });
    }
    for (const c of genaiCfg) {
      opts.push({ id: `cfg:${c.id}`, label: c.name, group: 'OCI GenAI' });
    }
    return opts;
  }, [genaiCfg]);

  const filteredModels = useMemo(() => {
    if (!modelSearch) return modelOptions;
    const q = modelSearch.toLowerCase();
    return modelOptions.filter((m) =>
      m.label.toLowerCase().includes(q) || m.group.toLowerCase().includes(q)
    );
  }, [modelOptions, modelSearch]);

  const modelGroups = useMemo(() => {
    const groups: Record<string, { id: string; label: string }[]> = {};
    for (const m of filteredModels) {
      if (!groups[m.group]) groups[m.group] = [];
      groups[m.group].push({ id: m.id, label: m.label });
    }
    return groups;
  }, [filteredModels]);

  const selectedModelLabel = useMemo(() => {
    const opt = modelOptions.find((m) => m.id === model);
    return opt?.label || model;
  }, [model, modelOptions]);

  /* ── Plan changes ── */
  const planChanges = useMemo(() => parsePlan(planOutput), [planOutput]);

  /* ── Current terminal output ── */
  const terminalOutput = useMemo(() => {
    if (terminalView === 'apply') return applyOutput;
    if (terminalView === 'destroy') return destroyOutput;
    return planOutput;
  }, [terminalView, planOutput, applyOutput, destroyOutput]);

  const terminalHeaderColor = useMemo(() => {
    if (terminalView === 'apply') return 'var(--gn)';
    if (terminalView === 'destroy') return 'var(--rd)';
    return 'var(--pp)';
  }, [terminalView]);

  /* ── Resource categories ── */
  const RESOURCE_CATEGORIES: Record<string, string> = {
    vcns: 'VCNs',
    subnets: 'Subnets',
    instances: 'Instances',
    gateways: 'Gateways',
    route_tables: 'Route Tables',
    security_lists: 'Security Lists',
    databases: 'Databases',
    block_volumes: 'Block Volumes',
    load_balancers: 'Load Balancers',
  };

  /* ═════════════════════════════════════════════════════════════════════
     RENDER
     ═════════════════════════════════════════════════════════════════════ */

  return (
    <div ref={containerRef}
      className="flex h-full animate-[fadeIn_.4s_ease] overflow-hidden"
      style={{ background: 'var(--bg)' }}>

      {/* ═══════════════ LEFT PANEL — CHAT ═══════════════ */}
      <div className="flex flex-col flex-1 relative" style={{ minWidth: 360, borderRight: '1px solid var(--bd)' }}>

        {/* ── History sidebar (collapsible) ── */}
        {historyOpen && (
          <div className="absolute inset-y-0 left-0 z-40 flex flex-col"
               style={{ width: 260, background: 'var(--bg1)', borderRight: '1px solid var(--bd)', boxShadow: 'var(--sh3)' }}>
            <div className="flex items-center gap-2 px-3 py-2.5 border-b" style={{ borderColor: 'var(--bd)' }}>
              <History size={16} style={{ color: 'var(--pp)' }} />
              <span className="text-[.78rem] font-bold" style={{ color: 'var(--t1)' }}>{t('tf.history')}</span>
              <div className="flex-1" />
              <button onClick={newChat}
                className="p-1 rounded cursor-pointer" style={{ color: 'var(--pp)' }} title={t('tf.newChat')}>
                <Plus size={16} />
              </button>
              <button onClick={() => setHistoryOpen(false)}
                className="p-1 rounded cursor-pointer" style={{ color: 'var(--t4)' }}>
                <X size={16} />
              </button>
            </div>
            <div className="flex-1 overflow-y-auto py-1">
              {sessionGroups.map((g) => (
                <div key={g.label} className="mb-1">
                  <div className="px-3 py-1 text-[.62rem] font-semibold uppercase tracking-wider"
                       style={{ color: 'var(--t4)' }}>{g.label}</div>
                  {g.items.map((s) => (
                    <div key={s.id}
                      className="flex items-center gap-2 px-3 py-1.5 cursor-pointer group transition-colors"
                      style={{
                        background: sessionId === s.id ? 'var(--bg3)' : 'transparent',
                        borderLeft: sessionId === s.id ? '2px solid var(--pp)' : '2px solid transparent',
                      }}
                      onClick={() => loadSession(s.id)}>
                      <span className="flex-1 text-[.72rem] truncate" style={{ color: 'var(--t2)' }}>
                        {s.title || t('chat.untitled')}
                      </span>
                      <button
                        className="opacity-0 group-hover:opacity-100 p-0.5 rounded cursor-pointer transition-opacity"
                        style={{ color: 'var(--rd)' }}
                        onClick={(e) => { e.stopPropagation(); deleteSession(s.id); }}
                        title={t('common.delete')}>
                        <Trash2 size={12} />
                      </button>
                    </div>
                  ))}
                </div>
              ))}
              {sessions.length === 0 && (
                <div className="px-3 py-4 text-center text-[.72rem]" style={{ color: 'var(--t4)' }}>
                  {t('tf.noConversations')}
                </div>
              )}
            </div>
          </div>
        )}

        {/* ── Chat toolbar ── */}
        <div className="flex items-center gap-1.5 px-3 py-2 border-b flex-wrap"
             style={{ borderColor: 'var(--bd)', background: 'var(--bg1)' }}>
          <button onClick={() => { setHistoryOpen(!historyOpen); if (!historyOpen) loadSessions(); }}
            className="p-1.5 rounded-lg cursor-pointer transition-colors"
            style={{ background: 'var(--bg3)', color: 'var(--t2)' }}
            title={t('tf.history')}>
            <History size={16} />
          </button>

          {/* Model dropdown */}
          <div className="relative">
            <button onClick={() => setModelDropdownOpen(!modelDropdownOpen)}
              className="flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg text-[.68rem] font-medium cursor-pointer transition-colors"
              style={{ background: 'var(--bg3)', color: 'var(--t2)', border: '1px solid var(--bd)' }}>
              <Code size={13} style={{ color: 'var(--pp)' }} />
              <span className="max-w-[100px] truncate">{selectedModelLabel}</span>
              <ChevronDown size={12} />
            </button>
            {modelDropdownOpen && (
              <div className="absolute top-full left-0 mt-1 z-30 w-64 rounded-xl overflow-hidden"
                   style={{ background: 'var(--bg1)', border: '1px solid var(--bd)', boxShadow: 'var(--sh3)' }}>
                <div className="px-2 py-1.5 border-b" style={{ borderColor: 'var(--bd)' }}>
                  <div className="flex items-center gap-1.5 px-2 py-1 rounded-lg"
                       style={{ background: 'var(--bg2)', border: '1px solid var(--bd)' }}>
                    <Search size={12} style={{ color: 'var(--t4)' }} />
                    <input type="text" value={modelSearch}
                      onChange={(e) => setModelSearch(e.target.value)}
                      placeholder={t('tf.searchModel')}
                      className="flex-1 bg-transparent text-[.7rem] outline-none"
                      style={{ color: 'var(--t1)' }}
                      autoFocus />
                  </div>
                </div>
                <div className="max-h-64 overflow-y-auto py-1">
                  {Object.entries(modelGroups).map(([group, items]) => (
                    <div key={group}>
                      <div className="px-3 py-1 text-[.6rem] font-semibold uppercase tracking-wider"
                           style={{ color: 'var(--t4)' }}>{group}</div>
                      {items.map((m) => (
                        <button key={m.id}
                          onClick={() => { setModel(m.id); setModelDropdownOpen(false); setModelSearch(''); }}
                          className="flex items-center gap-2 w-full px-3 py-1.5 text-left cursor-pointer transition-colors"
                          style={{
                            background: model === m.id ? 'var(--bg3)' : 'transparent',
                            color: 'var(--t2)',
                          }}>
                          {model === m.id && <Check size={12} style={{ color: 'var(--pp)' }} />}
                          <span className="text-[.72rem]">{m.label}</span>
                        </button>
                      ))}
                    </div>
                  ))}
                </div>
              </div>
            )}
          </div>

          {/* OCI Config */}
          <select value={ociConfig}
            onChange={(e) => setOciConfig(e.target.value)}
            className="px-2 py-1.5 rounded-lg text-[.68rem] cursor-pointer"
            style={{ background: 'var(--bg3)', color: 'var(--t2)', border: '1px solid var(--bd)' }}>
            <option value="">{t('tf.ociConfig')}</option>
            {ociCfg.map((c) => (
              <option key={c.id} value={c.id}>{c.tenancy_name || c.region}</option>
            ))}
          </select>

          {/* Compartment */}
          <select value={compartment}
            onChange={(e) => setCompartment(e.target.value)}
            className="px-2 py-1.5 rounded-lg text-[.68rem] cursor-pointer max-w-[140px]"
            style={{ background: 'var(--bg3)', color: 'var(--t2)', border: '1px solid var(--bd)' }}>
            <option value="">{t('tf.compartment')}</option>
            {compartments.map((c) => (
              <option key={c.id} value={c.id}>
                {'  '.repeat(c.depth)}{c.name}
              </option>
            ))}
          </select>

          {/* Region */}
          {region && (
            <span className="px-2 py-1 rounded-lg text-[.64rem]"
                  style={{ background: 'var(--bg3)', color: 'var(--t4)' }}>
              {region}
            </span>
          )}

          <div className="flex-1" />

          {/* Menu button */}
          <div className="relative">
            <button onClick={() => setMenuOpen(!menuOpen)}
              className="p-1.5 rounded-lg cursor-pointer transition-colors"
              style={{ background: 'var(--bg3)', color: 'var(--t3)' }} title={t('tf.settings')}>
              <Menu size={16} />
            </button>
            {menuOpen && (
              <div className="absolute right-0 top-full mt-1 z-30 w-60 rounded-xl overflow-hidden py-1"
                   style={{ background: 'var(--bg1)', border: '1px solid var(--bd)', boxShadow: 'var(--sh3)' }}>
                {/* Temperature slider */}
                <div className="px-3 py-2" onClick={(e) => e.stopPropagation()}>
                  <div className="flex items-center justify-between mb-1">
                    <span className="text-[.68rem] font-medium" style={{ color: 'var(--t3)' }}>Temperature</span>
                    <span className="text-[.68rem] font-mono" style={{ color: 'var(--t4)' }}>{tfTemperature.toFixed(1)}</span>
                  </div>
                  <input type="range" min={0} max={2} step={0.1} value={tfTemperature}
                    onChange={(e) => setTfTemperature(+e.target.value)}
                    className="w-full" style={{ accentColor: 'var(--pp)' }} />
                </div>
                <div style={{ borderTop: '1px solid var(--bd)', margin: '2px 0' }} />
                <button onClick={() => { setMenuOpen(false); /* TODO: system prompt editor */ }}
                  className="flex items-center gap-2 w-full px-3 py-2 text-left cursor-pointer transition-colors"
                  style={{ color: 'var(--t2)' }}>
                  <Settings size={14} />
                  <span className="text-[.72rem]">Editar System Prompt</span>
                </button>
                <button onClick={() => { setMenuOpen(false); /* TODO: refresh reference */ }}
                  className="flex items-center gap-2 w-full px-3 py-2 text-left cursor-pointer transition-colors"
                  style={{ color: 'var(--t2)' }}>
                  <RefreshCw size={14} />
                  <span className="text-[.72rem]">Refresh Reference</span>
                </button>
              </div>
            )}
          </div>
        </div>

        {/* ── Messages area ── */}
        <div className="flex-1 overflow-y-auto px-4 py-3 space-y-3"
             style={{ background: 'var(--bg)' }}>
          {messages.length === 0 && (
            <div className="flex flex-col items-center justify-center h-full text-center"
                 style={{ color: 'var(--t4)' }}>
              <Code size={36} className="mb-3 opacity-40" />
              <p className="text-sm font-medium mb-1">{t('tf.emptyTitle')}</p>
              <p className="text-[.72rem] opacity-60 max-w-xs">
                {t('tf.emptySubtitle')}
              </p>
            </div>
          )}
          {messages.map((msg) => (
            <div key={msg.id} className={`flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'}`}>
              <div
                className="max-w-[85%] rounded-xl px-3.5 py-2.5 text-[.78rem] leading-relaxed"
                style={{
                  background: msg.role === 'user' ? 'var(--pp)' : 'var(--bg2)',
                  color: msg.role === 'user' ? '#fff' : 'var(--t1)',
                  border: msg.role === 'user' ? 'none' : '1px solid var(--bd)',
                  overflowWrap: 'break-word',
                  wordBreak: 'break-word',
                  minWidth: 0,
                }}>
                {msg.role === 'assistant' && !msg.content && (
                  <span className="flex items-center gap-2" style={{ color: 'var(--t4)' }}>
                    <Loader2 size={14} className="animate-spin" /> {t('tf.thinking')}
                  </span>
                )}
                {msg.role === 'assistant' && msg.content ? (
                  <div className="prose prose-sm prose-invert max-w-none [&_pre]:rounded-lg [&_pre]:text-[.72rem] [&_pre]:overflow-x-auto [&_code]:text-[.72rem] [&_table]:text-[.68rem] [&_table]:overflow-x-auto" style={{ overflowWrap: 'break-word', wordBreak: 'break-word' }}>
                    <ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeHighlight]}>
                      {msg.content}
                    </ReactMarkdown>
                  </div>
                ) : msg.role === 'user' ? (
                  <span>{msg.content}</span>
                ) : null}
              </div>
            </div>
          ))}
          <div ref={chatEndRef} />
        </div>

        {/* ── Input area ── */}
        <div className="px-3 py-2 border-t" style={{ borderColor: 'var(--bd)', background: 'var(--bg1)' }}>
          <div className="flex items-end gap-2">
            <textarea
              ref={inputRef}
              value={chatInput}
              onChange={(e) => setChatInput(e.target.value)}
              onKeyDown={(e) => {
                if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); }
              }}
              placeholder={t('tf.placeholder')}
              rows={2}
              className="flex-1 resize-none rounded-xl px-3 py-2.5 text-[.78rem] outline-none"
              style={{ background: 'var(--bg2)', color: 'var(--t1)', border: '1px solid var(--bd)' }}
            />
            <button onClick={() => sendMessage()} disabled={sending || !chatInput.trim()}
              className="p-2.5 rounded-xl cursor-pointer transition-colors disabled:opacity-40"
              style={{ background: 'var(--pp)', color: '#fff' }}>
              {sending ? <Loader2 size={18} className="animate-spin" /> : <Send size={18} />}
            </button>
          </div>
        </div>
      </div>
      {/* end left panel (chat only) */}

      {/* ═══════════════ RIGHT PANEL — TERMINAL + BOTTOM PANEL ═══════════════ */}
      <div className="flex flex-col"
           style={{ width: '50%', minWidth: 280, borderLeft: '1px solid var(--bd)' }}>
        {/* Terminal header */}
        <div className="flex items-center gap-2 px-3 py-2 border-b shrink-0"
             style={{ borderColor: 'var(--bd)', background: 'var(--bg1)' }}>
          <Terminal size={16} style={{ color: terminalHeaderColor }} />
          <span className="text-[.78rem] font-bold" style={{ color: 'var(--t1)' }}>{t('tf.terminal')}</span>
          <div className="flex-1" />

          {/* Terminal view toggle */}
          {(['plan', 'apply', 'destroy'] as const).map((view) => (
            <button key={view}
              onClick={() => setTerminalView(view)}
              className="px-2 py-0.5 rounded text-[.62rem] font-medium cursor-pointer transition-colors"
              style={{
                background: terminalView === view
                  ? (view === 'plan' ? 'var(--pp)' : view === 'apply' ? 'var(--gn)' : 'var(--rd)')
                  : 'var(--bg3)',
                color: terminalView === view ? '#fff' : 'var(--t4)',
              }}>
              {view === 'plan' ? t('tf.plan') : view === 'apply' ? t('tf.apply') : t('tf.destroy')}
            </button>
          ))}

          {running && (
            <span className="flex items-center gap-1.5 text-[.68rem]" style={{ color: 'var(--yl)' }}>
              <Loader2 size={13} className="animate-spin" /> Executando...
            </span>
          )}
        </div>

        {/* Terminal body */}
        <pre ref={terminalRef}
          className="flex-1 overflow-y-auto p-3 text-[.68rem] leading-relaxed"
          style={{
            background: '#0d0d0d',
            fontFamily: 'var(--fm)',
            color: 'var(--t3)',
            margin: 0,
          }}>
          {!terminalOutput && !running && (
            <span style={{ color: 'var(--t4)', opacity: 0.5 }}>
              {`# Terminal de saida do Terraform\n# Execute Plan, Apply ou Destroy para ver os resultados aqui`}
            </span>
          )}
          {wsError && terminalView === 'plan' && (
            <span style={{ color: 'var(--rd)', fontWeight: 700 }}>
              {'Error: ' + wsError + '\n\n'}
            </span>
          )}
          {terminalOutput && terminalOutput.split('\n').map((line, i) => {
            const { color, bold } = colorTerminalLine(line);
            return (
              <span key={i} style={{ color, fontWeight: bold ? 700 : 400 }}>
                {line + '\n'}
              </span>
            );
          })}
        </pre>

        {/* ═══════════════ BOTTOM PANEL (FILES / PLAN / RESOURCES) ═══════════════ */}

        {/* Resize handle */}
        <div className="h-1.5 cursor-row-resize flex items-center justify-center shrink-0"
             style={{ background: 'var(--bg1)', borderTop: '1px solid var(--bd)' }}
             onMouseDown={onResizeStart}>
          <div className="w-8 h-0.5 rounded-full" style={{ background: 'var(--t4)', opacity: 0.5 }} />
        </div>

        {/* Bottom panel content */}
        <div className="flex flex-col shrink-0 overflow-hidden" style={{ height: bottomHeight }}>
          {/* Tab bar */}
          <div className="flex items-center gap-1 px-3 py-1.5 border-b shrink-0"
               style={{ borderColor: 'var(--bd)', background: 'var(--bg1)' }}>
            {([
              { key: 'files' as const, icon: <FileCode size={13} />, label: t('tf.files') },
              { key: 'plan' as const, icon: <Layers size={13} />, label: t('tf.plan') },
              { key: 'resources' as const, icon: <FolderOpen size={13} />, label: t('tf.resources') },
            ]).map((tab) => (
              <button key={tab.key}
                onClick={() => setBottomTab(tab.key)}
                className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-[.68rem] font-medium cursor-pointer transition-colors"
                style={{
                  background: bottomTab === tab.key ? 'var(--pp)' : 'transparent',
                  color: bottomTab === tab.key ? '#fff' : 'var(--t3)',
                }}>
                {tab.icon}
                {tab.label}
                {tab.key === 'files' && files.length > 0 && (
                  <span className="ml-1 px-1.5 py-0 rounded-full text-[.58rem]"
                        style={{ background: 'var(--bg3)', color: 'var(--t3)' }}>
                    {files.length}
                  </span>
                )}
              </button>
            ))}

            <div className="flex-1" />

            {/* Status badge */}
            {wsId && <StatusBadge status={wsStatus} />}

            {/* Action buttons */}
            <div className="flex items-center gap-1 ml-2">
              {(wsStatus === 'draft' || wsStatus === 'destroyed' || wsStatus === 'failed' || wsStatus === 'planned' || wsStatus === 'applied') && !running && (
                <button onClick={doPlan}
                  className="flex items-center gap-1 px-2 py-1 rounded-md text-[.66rem] font-medium cursor-pointer transition-colors"
                  style={{ background: 'var(--pp)', color: '#fff', border: '1px solid var(--pp)' }}>
                  {wsStatus === 'planned' || wsStatus === 'applied' || wsStatus === 'failed' ? (
                    <><RefreshCw size={12} /> Re-plan</>
                  ) : (
                    <><Play size={12} /> {t('tf.plan')}</>
                  )}
                </button>
              )}
              {wsStatus === 'planned' && !running && (
                <button onClick={doApply}
                  className="flex items-center gap-1 px-2 py-1 rounded-md text-[.66rem] font-medium cursor-pointer transition-colors"
                  style={{ background: 'var(--gn)', color: '#fff', border: '1px solid var(--gn)' }}>
                  <Rocket size={12} /> {t('tf.apply')}
                </button>
              )}
              {wsStatus === 'failed' && !running && (
                <button onClick={doAutoFix}
                  className="flex items-center gap-1 px-2 py-1 rounded-md text-[.66rem] font-medium cursor-pointer transition-colors"
                  style={{ background: 'var(--yl)', color: '#000', border: '1px solid var(--yl)' }}>
                  <Wrench size={12} /> Auto-corrigir
                </button>
              )}
              {wsStatus === 'applied' && !running && (
                <button onClick={() => { setConfirmDestroy(true); setDestroyInput(''); }}
                  className="flex items-center gap-1 px-2 py-1 rounded-md text-[.66rem] font-medium cursor-pointer transition-colors"
                  style={{ background: 'var(--rd)', color: '#fff', border: '1px solid var(--rd)' }}>
                  <Flame size={12} /> {t('tf.destroy')}
                </button>
              )}
              {wsStatus === 'failed' && wsId && applyOutput && !running && (
                <button onClick={doRollback}
                  className="flex items-center gap-1 px-2 py-1 rounded-md text-[.66rem] font-medium cursor-pointer transition-colors"
                  style={{ background: 'var(--bg3)', color: 'var(--t2)', border: '1px solid var(--bd)' }}>
                  <RotateCcw size={12} /> {t('tf.rollback')}
                </button>
              )}
              {running && (
                <button onClick={doCancel}
                  className="flex items-center gap-1 px-2 py-1 rounded-md text-[.66rem] font-medium cursor-pointer transition-colors"
                  style={{ background: 'var(--rd)', color: '#fff', border: '1px solid var(--rd)' }}>
                  <Ban size={12} /> {t('tf.cancel')}
                </button>
              )}
              {files.length > 0 && (
                <button onClick={() => downloadZip(files, wsName || 'terraform')}
                  className="flex items-center gap-1 px-2 py-1 rounded-md text-[.66rem] font-medium cursor-pointer transition-colors"
                  style={{ background: 'var(--bg3)', color: 'var(--t2)', border: '1px solid var(--bd)' }}>
                  <Download size={12} /> ZIP
                </button>
              )}
            </div>
          </div>

          {/* Tab content */}
          <div className="flex-1 overflow-y-auto" style={{ background: 'var(--bg)' }}>
            {/* ── FILES TAB ── */}
            {bottomTab === 'files' && (
              <div className="p-3">
                {files.length === 0 && (
                  <div className="flex flex-col items-center justify-center py-8 text-center"
                       style={{ color: 'var(--t4)' }}>
                    <FileCode size={28} className="mb-2 opacity-40" />
                    <p className="text-[.72rem]">{t('tf.noFiles')}</p>
                    <p className="text-[.64rem] opacity-60 mt-1">{t('tf.emptySubtitle')}</p>
                  </div>
                )}
                {files.map((file, idx) => (
                  <div key={file.name + idx} className="mb-2 rounded-lg overflow-hidden"
                       style={{ border: '1px solid var(--bd)', background: 'var(--bg1)' }}>
                    <div className="flex items-center gap-2 px-3 py-1.5 cursor-pointer"
                         style={{ background: 'var(--bg2)', borderBottom: editingFileIdx === idx ? '1px solid var(--bd)' : 'none' }}
                         onClick={() => setEditingFileIdx(editingFileIdx === idx ? null : idx)}>
                      <File size={13} style={{ color: 'var(--pp)' }} />
                      <span className="text-[.72rem] font-medium" style={{ color: 'var(--t1)' }}>{file.name}</span>
                      <span className="text-[.62rem]" style={{ color: 'var(--t4)' }}>
                        {file.size > 1024 ? (file.size / 1024).toFixed(1) + ' KB' : file.size + ' B'}
                      </span>
                      <div className="flex-1" />
                      <div className="flex items-center gap-1" onClick={(e) => e.stopPropagation()}>
                        <button onClick={() => copyFile(file.content)}
                          className="p-1 rounded cursor-pointer" style={{ color: 'var(--t4)' }} title={t('tf.copy')}>
                          <Copy size={12} />
                        </button>
                        <button onClick={() => downloadFile(file)}
                          className="p-1 rounded cursor-pointer" style={{ color: 'var(--t4)' }} title={t('common.download')}>
                          <Download size={12} />
                        </button>
                      </div>
                      <ChevronRight size={13}
                        className={`transition-transform ${editingFileIdx === idx ? 'rotate-90' : ''}`}
                        style={{ color: 'var(--t4)' }} />
                    </div>
                    {editingFileIdx === idx && (
                      <div className="relative">
                        <textarea
                          value={file.content}
                          onChange={(e) => updateFileContent(idx, e.target.value)}
                          className="w-full resize-none p-3 text-[.72rem] outline-none"
                          style={{
                            background: 'var(--bg1)',
                            color: 'var(--t1)',
                            fontFamily: 'var(--fm)',
                            minHeight: 200,
                            maxHeight: 400,
                          }}
                          spellCheck={false}
                        />
                        <div className="flex items-center gap-1 px-3 py-1.5 border-t"
                             style={{ borderColor: 'var(--bd)', background: 'var(--bg2)' }}>
                          <button onClick={() => {
                            if (wsId) {
                              terraformApi.updateCode(wsId, joinFiles(files)).then(() => {}).catch(() => {});
                            }
                          }}
                            className="flex items-center gap-1 px-2 py-1 rounded-md text-[.64rem] font-medium cursor-pointer"
                            style={{ background: 'var(--pp)', color: '#fff' }}>
                            <Save size={11} /> Salvar
                          </button>
                        </div>
                      </div>
                    )}
                  </div>
                ))}
              </div>
            )}

            {/* ── PLAN TAB ── */}
            {bottomTab === 'plan' && (
              <div className="p-3">
                {planChanges.length === 0 && !planOutput && (
                  <div className="flex flex-col items-center justify-center py-8 text-center"
                       style={{ color: 'var(--t4)' }}>
                    <Layers size={28} className="mb-2 opacity-40" />
                    <p className="text-[.72rem]">Nenhum plan executado</p>
                    <p className="text-[.64rem] opacity-60 mt-1">Execute Plan para ver os recursos</p>
                  </div>
                )}
                {planChanges.length > 0 && (
                  <div className="space-y-1">
                    <div className="text-[.72rem] font-semibold mb-2" style={{ color: 'var(--t1)' }}>
                      Recursos ({planChanges.length})
                    </div>
                    {planChanges.map((ch, i) => (
                      <div key={i}
                        className="flex items-center gap-2 px-3 py-1.5 rounded-lg"
                        style={{ background: 'var(--bg1)', border: '1px solid var(--bd)' }}>
                        <span className="w-5 h-5 flex items-center justify-center rounded text-[.7rem] font-bold"
                              style={{
                                background: ch.action === '+' ? 'var(--gnl)' : ch.action === '~' ? 'var(--yll)' : 'var(--rdl)',
                                color: ch.action === '+' ? 'var(--gn)' : ch.action === '~' ? 'var(--yl)' : 'var(--rd)',
                              }}>
                          {ch.action}
                        </span>
                        <span className="text-[.7rem] font-mono" style={{ color: 'var(--t2)' }}>{ch.type}</span>
                        <span className="text-[.68rem]" style={{ color: 'var(--t4)' }}>.{ch.name}</span>
                      </div>
                    ))}
                  </div>
                )}
                {planChanges.length === 0 && planOutput && (
                  <div className="text-[.72rem]" style={{ color: 'var(--t3)' }}>
                    <p className="mb-2 font-semibold" style={{ color: 'var(--t1)' }}>Plan Output</p>
                    <pre className="whitespace-pre-wrap text-[.68rem]" style={{ fontFamily: 'var(--fm)' }}>
                      {planOutput.slice(0, 2000)}
                    </pre>
                  </div>
                )}
              </div>
            )}

            {/* ── RESOURCES TAB ── */}
            {bottomTab === 'resources' && (
              <div className="p-3">
                <div className="flex items-center gap-2 mb-3">
                  <button onClick={loadResources} disabled={resourcesLoading}
                    className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-[.68rem] font-medium cursor-pointer transition-colors"
                    style={{ background: 'var(--pp)', color: '#fff' }}>
                    {resourcesLoading ? <Loader2 size={13} className="animate-spin" /> : <RefreshCw size={13} />}
                    {resourcesLoading ? 'Carregando...' : 'Carregar Recursos'}
                  </button>
                  {ociConfig && compartment && (
                    <span className="text-[.64rem]" style={{ color: 'var(--t4)' }}>
                      {ociCfg.find((c) => c.id === ociConfig)?.tenancy_name} / {region}
                    </span>
                  )}
                </div>

                {Object.keys(resources).length === 0 && !resourcesLoading && (
                  <div className="flex flex-col items-center justify-center py-6 text-center"
                       style={{ color: 'var(--t4)' }}>
                    <FolderOpen size={28} className="mb-2 opacity-40" />
                    <p className="text-[.72rem]">Nenhum recurso carregado</p>
                    <p className="text-[.64rem] opacity-60 mt-1">Selecione OCI Config + Compartment e clique em Carregar</p>
                  </div>
                )}

                {Object.entries(resources).length > 0 && (
                  <div className="grid grid-cols-2 gap-2">
                    {Object.entries(resources).map(([cat, items]) => {
                      const label = RESOURCE_CATEGORIES[cat] || cat;
                      const display = items.slice(0, 10);
                      const overflow = items.length - 10;
                      return (
                        <div key={cat} className="rounded-lg p-2.5"
                             style={{ background: 'var(--bg1)', border: '1px solid var(--bd)' }}>
                          <div className="flex items-center gap-1.5 mb-1.5">
                            <span className="text-[.68rem] font-semibold" style={{ color: 'var(--t1)' }}>{label}</span>
                            <span className="text-[.6rem] px-1.5 py-0 rounded-full"
                                  style={{ background: 'var(--bg3)', color: 'var(--t4)' }}>
                              {items.length}
                            </span>
                          </div>
                          {display.map((item: any, i: number) => (
                            <div key={i} className="text-[.66rem] truncate py-0.5" style={{ color: 'var(--t3)' }}>
                              {item.display_name || item.name || item.id || JSON.stringify(item).slice(0, 60)}
                            </div>
                          ))}
                          {overflow > 0 && (
                            <div className="text-[.62rem] mt-1" style={{ color: 'var(--t4)' }}>
                              +{overflow} mais
                            </div>
                          )}
                        </div>
                      );
                    })}
                  </div>
                )}
              </div>
            )}
          </div>
        </div>
      </div>

      {/* ═══════════════ MODALS ═══════════════ */}

      {/* Destroy confirm */}
      {confirmDestroy && (
        <Modal onClose={() => setConfirmDestroy(false)}>
          <h3 className="text-sm font-bold mb-2" style={{ color: 'var(--t1)' }}>{t('ws.confirmDestroy')}</h3>
          <p className="text-[.76rem] mb-3" style={{ color: 'var(--t2)' }}>
            {t('ws.confirmDestroyMsg')}
          </p>
          <input type="text" value={destroyInput}
            onChange={(e) => setDestroyInput(e.target.value)}
            placeholder="DESTROY"
            className="w-full px-3 py-2 rounded-lg text-sm text-center"
            style={{
              background: 'var(--bg2)', color: 'var(--t1)',
              border: `1px solid ${destroyInput === 'DESTROY' ? 'var(--gn)' : 'var(--rd)'}`,
            }}
            autoFocus
            onKeyDown={(e) => { if (e.key === 'Enter') doDestroy(); }}
          />
          <div className="flex justify-end gap-2 mt-3">
            <button onClick={() => setConfirmDestroy(false)}
              className="px-3 py-1.5 rounded-lg text-[.76rem] font-medium cursor-pointer"
              style={{ background: 'var(--bg3)', color: 'var(--t2)', border: '1px solid var(--bd)' }}>
              {t('tf.cancel')}
            </button>
            <button onClick={doDestroy} disabled={destroyInput !== 'DESTROY'}
              className="px-3 py-1.5 rounded-lg text-[.76rem] font-medium cursor-pointer disabled:opacity-40"
              style={{ background: 'var(--rd)', color: '#fff', border: '1px solid var(--rd)' }}>
              {t('tf.destroy')}
            </button>
          </div>
        </Modal>
      )}

      {/* Close model dropdown on click outside */}
      {modelDropdownOpen && (
        <div className="fixed inset-0 z-20" onClick={() => { setModelDropdownOpen(false); setModelSearch(''); }} />
      )}
      {menuOpen && (
        <div className="fixed inset-0 z-20" onClick={() => setMenuOpen(false)} />
      )}
    </div>
  );
}
