// App.jsx — entry point: routing, state, toasts, intake slide-over
const { useState, useEffect, useMemo, useCallback, useRef } = React;

const SLACK_CHANNEL_URL = 'https://cortexio.slack.com/archives/C0AN4KKPXFD';

const DEFAULTS = /*EDITMODE-BEGIN*/{
  "density": "comfortable",
  "brandColor": "#7458DB",
  "layout": "kanban",
  "showTweaks": true
}/*EDITMODE-END*/;

const BRAND_PRESETS = [
  { name: 'Cortex purple', color: '#7458DB' },
  { name: 'Indigo',        color: '#4f46e5' },
  { name: 'Emerald',       color: '#059669' },
  { name: 'Amber',         color: '#d97706' },
  { name: 'Rose',          color: '#e11d48' },
];

function applyBrand(hex) {
  const root = document.documentElement;
  root.style.setProperty('--brand-500', hex);
  root.style.setProperty('--brand-600', shade(hex, -10));
  root.style.setProperty('--brand-400', shade(hex, 10));
  root.style.setProperty('--brand-700', shade(hex, -20));
}
function shade(hex, pct) {
  const n = parseInt(hex.slice(1), 16);
  let r = (n >> 16) & 255, g = (n >> 8) & 255, b = n & 255;
  const a = pct / 100;
  r = Math.max(0, Math.min(255, Math.round(r + (a < 0 ? r * a : (255 - r) * a))));
  g = Math.max(0, Math.min(255, Math.round(g + (a < 0 ? g * a : (255 - g) * a))));
  b = Math.max(0, Math.min(255, Math.round(b + (a < 0 ? b * a : (255 - b) * a))));
  return '#' + [r,g,b].map(x => x.toString(16).padStart(2,'0')).join('');
}

function isEditableElement(el) {
  if (!el) return false;
  const tag = el.tagName;
  return el.isContentEditable || tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT';
}

function normalizeCommandText(text) {
  return String(text || '').toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim();
}

function fuzzyScore(query, text) {
  const q = normalizeCommandText(query).replace(/\s+/g, '');
  const t = normalizeCommandText(text);
  const compact = t.replace(/\s+/g, '');
  if (!q) return 1;
  let cursor = 0;
  let score = 0;
  let streak = 0;
  for (const char of q) {
    const idx = compact.indexOf(char, cursor);
    if (idx === -1) return -1;
    const contiguous = idx === cursor;
    streak = contiguous ? streak + 1 : 0;
    score += contiguous ? 6 + streak * 2 : 2;
    cursor = idx + 1;
  }
  if (t.includes(normalizeCommandText(query))) score += 18;
  return score - Math.max(0, compact.length - q.length) * 0.06;
}

function CommandPalette({ open, query, items, activeIndex, onClose, onQueryChange, onSelect, onHover }) {
  const inputRef = useRef(null);

  useEffect(() => {
    if (!open) return;
    const id = window.setTimeout(() => inputRef.current && inputRef.current.focus(), 20);
    return () => window.clearTimeout(id);
  }, [open]);

  if (!open) return null;

  return (
    <>
      <div className="cmdk-backdrop" onClick={onClose} />
      <div className="cmdk-panel" role="dialog" aria-modal="true" aria-label="Command palette">
        <div className="cmdk-search">
          <Icon name="MagnifyingGlassIcon" size={16} weight="bold" />
          <input
            ref={inputRef}
            className="cmdk-input"
            value={query}
            onChange={e => onQueryChange(e.target.value)}
            placeholder="Jump to a ticket, page, or view…"
          />
          <button type="button" className="cmdk-close" onClick={onClose} aria-label="Close command palette">
            <Icon name="XIcon" size={14} />
          </button>
        </div>
        <div className="cmdk-list">
          {items.length === 0 ? (
            <div className="cmdk-empty">
              <div className="cmdk-empty-title">No matches</div>
              <div className="cmdk-empty-sub">Try a ticket ID, team, or page name.</div>
            </div>
          ) : items.map((item, idx) => (
            <button
              type="button"
              key={`${item.kind}-${item.id}`}
              className={`cmdk-item ${idx === activeIndex ? 'active' : ''}`}
              onMouseEnter={() => onHover(idx)}
              onClick={() => onSelect(item)}
            >
              <span className={`cmdk-item-icon kind-${item.kind}`}>
                <Icon name={item.icon} size={14} weight={item.kind === 'ticket' ? 'regular' : 'fill'} />
              </span>
              <span className="cmdk-item-body">
                <span className="cmdk-item-title">{item.title}</span>
                <span className="cmdk-item-sub">{item.subtitle}</span>
              </span>
              <span className="cmdk-item-meta">{item.meta}</span>
            </button>
          ))}
        </div>
        <div className="cmdk-foot">
          <span><kbd>↑</kbd><kbd>↓</kbd> navigate</span>
          <span><kbd>Enter</kbd> open</span>
          <span><kbd>Esc</kbd> close</span>
        </div>
      </div>
    </>
  );
}

// ---------- useCountUp hook ----------

function useCountUp(target, duration = 900) {
  const [value, setValue] = useState(0);
  const [counting, setCounting] = useState(false);
  const rafRef = useRef(null);
  const startRef = useRef(null);

  const start = useCallback(() => {
    if (rafRef.current) cancelAnimationFrame(rafRef.current);
    startRef.current = null;
    setValue(0);
    setCounting(true);

    function tick(ts) {
      if (!startRef.current) startRef.current = ts;
      const elapsed = ts - startRef.current;
      const progress = Math.min(elapsed / duration, 1);
      // ease-out cubic
      const eased = 1 - Math.pow(1 - progress, 3);
      setValue(target * eased);
      if (progress < 1) {
        rafRef.current = requestAnimationFrame(tick);
      } else {
        setValue(target);
        setCounting(false);
      }
    }
    rafRef.current = requestAnimationFrame(tick);
  }, [target, duration]);

  useEffect(() => () => { if (rafRef.current) cancelAnimationFrame(rafRef.current); }, []);

  return { value, counting, start };
}

// ---------- Toast system ----------

function ToastItem({ toast, onRemove }) {
  const [phase, setPhase] = useState('entering');

  useEffect(() => {
    // switch to idle after enter animation completes
    const t1 = setTimeout(() => setPhase('idle'), 300);
    // start exit 4.7s in so removal fires at 5s total
    const t2 = setTimeout(() => setPhase('exiting'), 4700);
    const t3 = setTimeout(() => onRemove(toast.id), 4960);
    return () => { clearTimeout(t1); clearTimeout(t2); clearTimeout(t3); };
  }, [toast.id, onRemove]);

  return (
    <div className={`toast ${toast.type || ''} ${phase === 'entering' ? 'entering' : phase === 'exiting' ? 'exiting' : ''}`}>
      <Icon name={toast.type === 'success' ? 'CheckCircleIcon' : toast.type === 'error' ? 'WarningIcon' : 'BellIcon'}
            size={15} weight="fill" style={{ flex: '0 0 auto' }} />
      <span dangerouslySetInnerHTML={{ __html: toast.message }} />
    </div>
  );
}

function ToastContainer({ toasts, onRemoveToast }) {
  if (!toasts.length) return null;
  return (
    <div className="toast-container">
      {toasts.map(t => (
        <ToastItem key={t.id} toast={t} onRemove={onRemoveToast} />
      ))}
    </div>
  );
}

// ---------- Slack message formatter ----------

function formatSlackMessage(ticket) {
  const team = TEAMS.find(t => t.id === ticket.requester);
  const prio = PRIORITIES[ticket.priority];
  const prioEmoji = { urgent: ':red_circle:', high: ':large_yellow_circle:', med: ':white_circle:', low: ':white_circle:' }[ticket.priority] || '';
  const bt = '`';
  const lines = [
    ':memo: *New GTM Eng request \u00b7 ' + bt + ticket.id + bt + '*',
    '',
    `*${ticket.title}*`,
    '',
    `>When ${ticket.jtbd.when}, I need ${ticket.jtbd.need}, so that ${ticket.jtbd.outcome}.`,
    '',
    `*Problem:* ${ticket.problem}`,
  ];
  if (ticket.impactStatement) {
    lines.push('', `*Estimated impact:* ${ticket.impactStatement}`);
  }
  lines.push('', `${prioEmoji} ${prio?.label || ''} · ${team?.label || ''} · in Triage`);
  return lines.join('\n');
}

// ---------- App ----------

function App() {
  const VALID_NAV = new Set(['Home', 'Inbox', 'Intake', 'Playbooks']);
  const NAV_LABELS = { Home: 'Overview', Inbox: 'Inbox', Intake: 'New requests', Playbooks: 'Playbooks' };
  const [nav, setNavRaw] = useState(() => {
    const saved = localStorage.getItem('gtm.nav');
    return VALID_NAV.has(saved) ? saved : 'Inbox';
  });
  const [pageKey, setPageKey] = useState(0);
  const setNav = useCallback((id) => {
    setNavRaw(id);
    setPageKey(k => k + 1);
  }, []);
  const [view, setView] = useState(() => localStorage.getItem('gtm.view') || 'view-all');
  const [selectedId, setSelectedId] = useState(() => localStorage.getItem('gtm.ticket') || 'GTM-142');
  const [tweaks, setTweaks] = useState(DEFAULTS);
  const [darkMode, setDarkMode] = useState(() => localStorage.getItem('gtm.dark') === 'true');
  const [editMode, setEditMode] = useState(false);
  const [ticketVer, setTicketVer] = useState(0); // bumped when TICKETS mutated
  const [toasts, setToasts] = useState([]);
  const [intakeOpen, setIntakeOpen] = useState(false);
  const [commandOpen, setCommandOpen] = useState(false);
  const [commandQuery, setCommandQuery] = useState('');
  const [commandIndex, setCommandIndex] = useState(0);

  useEffect(() => { localStorage.setItem('gtm.nav', nav); }, [nav]);
  useEffect(() => { if (view) localStorage.setItem('gtm.view', view); }, [view]);
  useEffect(() => { if (selectedId) localStorage.setItem('gtm.ticket', selectedId); }, [selectedId]);
  useEffect(() => { document.body.classList.toggle('density-compact', tweaks.density === 'compact'); }, [tweaks.density]);
  useEffect(() => { applyBrand(tweaks.brandColor); }, [tweaks.brandColor]);
  useEffect(() => {
    document.documentElement.classList.toggle('dark', darkMode);
    localStorage.setItem('gtm.dark', darkMode);
  }, [darkMode]);
  useEffect(() => {
    const handler = (e) => {
      if (!e.data || typeof e.data !== 'object') return;
      if (e.data.type === '__activate_edit_mode') setEditMode(true);
      else if (e.data.type === '__deactivate_edit_mode') setEditMode(false);
    };
    window.addEventListener('message', handler);
    window.parent.postMessage({ type: '__edit_mode_available' }, '*');
    return () => window.removeEventListener('message', handler);
  }, []);
  useEffect(() => {
    const handler = (e) => {
      if ((e.metaKey || e.ctrlKey) && e.key === ',') {
        e.preventDefault();
        setEditMode(prev => !prev);
      }
    };
    window.addEventListener('keydown', handler);
    return () => window.removeEventListener('keydown', handler);
  }, []);

  const setLayout = useCallback((layout) => {
    setTweaks(prev => ({ ...prev, layout }));
  }, []);

  const openTicket = useCallback((id) => {
    setSelectedId(id);
    setNav('Inbox');
    setView('view-all');
    setLayout('full-detail');
  }, [setLayout]);

  const commandItems = useMemo(() => {
    const pageItems = Object.entries(NAV_LABELS).map(([id, label]) => ({
      id,
      kind: 'page',
      icon: GTM_MENU.find(item => item.id === id)?.icon || 'HouseIcon',
      title: label,
      subtitle: 'Page',
      meta: 'Jump',
      action: () => setNav(id),
      search: `${label} page ${id}`,
    }));
    const viewItems = GTM_VIEWS.map(item => ({
      id: item.id,
      kind: 'view',
      icon: item.icon,
      title: item.label,
      subtitle: 'Inbox view',
      meta: `${TICKETS.filter(item.filter).length}`,
      action: () => {
        setNav('Inbox');
        setView(item.id);
      },
      search: `${item.label} inbox ${item.id}`,
    }));
    const ticketItems = TICKETS.map(ticket => {
      const team = TEAMS.find(t => t.id === ticket.requester);
      const status = STATUSES.find(s => s.id === ticket.status);
      return {
        id: ticket.id,
        kind: 'ticket',
        icon: status?.icon || 'TrayIcon',
        title: `${ticket.id} · ${ticket.title}`,
        subtitle: `${team?.label || 'Unknown team'} · ${status?.label || 'Ticket'}`,
        meta: PRIORITIES[ticket.priority]?.label || '',
        action: () => openTicket(ticket.id),
        search: `${ticket.id} ${ticket.title} ${team?.label || ''} ${ticket.problem || ''} ${ticket.jtbd?.outcome || ''}`,
      };
    });
    const items = [...ticketItems, ...viewItems, ...pageItems];
    const ranked = items
      .map(item => ({ item, score: fuzzyScore(commandQuery, item.search) }))
      .filter(({ score }) => score >= 0)
      .sort((a, b) => b.score - a.score || a.item.title.localeCompare(b.item.title))
      .map(({ item }) => item);
    return (commandQuery ? ranked : items).slice(0, 10);
  }, [commandQuery, openTicket, ticketVer]);

  useEffect(() => {
    setCommandIndex(0);
  }, [commandQuery, commandOpen]);

  const runCommand = useCallback((item) => {
    if (!item) return;
    item.action();
    setCommandOpen(false);
    setCommandQuery('');
  }, []);

  // Keyboard shortcuts
  useEffect(() => {
    const handler = (e) => {
      const editable = isEditableElement(document.activeElement);
      if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') {
        e.preventDefault();
        setCommandOpen(open => !open);
        return;
      }
      if (commandOpen) {
        if (e.key === 'Escape') {
          setCommandOpen(false);
          setCommandQuery('');
        } else if (e.key === 'ArrowDown') {
          e.preventDefault();
          setCommandIndex(i => Math.min(i + 1, Math.max(0, commandItems.length - 1)));
        } else if (e.key === 'ArrowUp') {
          e.preventDefault();
          setCommandIndex(i => Math.max(0, i - 1));
        } else if (e.key === 'Enter') {
          e.preventDefault();
          runCommand(commandItems[commandIndex]);
        }
        return;
      }
      if (e.key.toLowerCase() === 'c' && !e.metaKey && !e.ctrlKey && !editable) {
        setIntakeOpen(true);
      }
      if (e.key === 'Escape') setIntakeOpen(false);
    };
    window.addEventListener('keydown', handler);
    return () => window.removeEventListener('keydown', handler);
  }, [commandItems, commandIndex, commandOpen, runCommand]);

  const setTweak = (k, v) => {
    setTweaks(prev => {
      const next = { ...prev, [k]: v };
      window.parent.postMessage({ type: '__edit_mode_set_keys', edits: { [k]: v } }, '*');
      return next;
    });
  };

  const removeToast = useCallback((id) => {
    setToasts(t => t.filter(x => x.id !== id));
  }, []);

  const showToast = useCallback((message, type = '') => {
    const id = Date.now() + Math.random();
    setToasts(t => [...t, { id, message, type }]);
  }, []);

  const addTicket = useCallback((ticket) => {
    TICKETS.unshift(ticket);
    setTicketVer(v => v + 1);
    setSelectedId(ticket.id);
    setNav('Inbox');
    setView('view-triage');
  }, []);

  const postTicketToSlack = useCallback((ticket) => {
    const msg = formatSlackMessage(ticket);
    if (navigator.clipboard && navigator.clipboard.writeText) {
      navigator.clipboard.writeText(msg).then(() => {
        showToast(
          `Copied to clipboard — <a href="${SLACK_CHANNEL_URL}" target="_blank">open #gtm-funnel →</a>`,
          'success'
        );
      }).catch(() => showToast('Could not access clipboard', 'error'));
    } else {
      // fallback: select a temporary textarea
      const ta = document.createElement('textarea');
      ta.value = msg;
      document.body.appendChild(ta);
      ta.select();
      document.execCommand('copy');
      document.body.removeChild(ta);
      showToast(`Copied — <a href="${SLACK_CHANNEL_URL}" target="_blank">open #gtm-funnel →</a>`, 'success');
    }
  }, [showToast]);

  const handleAddTicket = useCallback((ticket, postToSlack) => {
    addTicket(ticket);
    setIntakeOpen(false);
    showToast(`${ticket.id} added to triage`, 'success');
    if (postToSlack) {
      setTimeout(() => postTicketToSlack(ticket), 700);
    }
  }, [addTicket, postTicketToSlack, showToast]);

  const handleAddComment = useCallback(() => {
    setTicketVer(v => v + 1);
  }, []);

  let page;
  if (nav === 'Home') {
    page = <HomePage onNavigate={setNav} onOpenTicket={openTicket} onSelectView={setView} onNewRequest={() => setIntakeOpen(true)} ticketVer={ticketVer} />;
  } else if (nav === 'Intake') {
    page = <IntakePage onOpenTicket={openTicket} onNewRequest={() => setIntakeOpen(true)} ticketVer={ticketVer} />;
  } else if (nav === 'Inbox') {
    page = <InboxPage view={view} onSelectView={setView} layout={tweaks.layout} selectedId={selectedId}
                      onSelect={setSelectedId} onOpenTicket={openTicket} onChangeLayout={setLayout}
                      onPostToSlack={postTicketToSlack} ticketVer={ticketVer} onAddComment={handleAddComment} />;
  } else if (nav === 'Playbooks') {
    page = <PlaybooksPage onNewRequest={() => setIntakeOpen(true)} />;
  } else {
    page = (
      <div className="page-head"><div><div className="title-row"><h1>{nav}</h1></div>
        <div className="page-desc">Coming soon.</div></div></div>
    );
  }

  const crumbs = useMemo(() => {
    return ['GTM Eng', NAV_LABELS[nav] || nav];
  }, [nav]);

  const isInbox = nav === 'Inbox';

  return (
    <div className="app-shell">
      <Sidebar current={nav} view={view} onSelectView={setView}
               onNavigate={(id) => setNav(id)}
               onOpenCommandPalette={() => setCommandOpen(true)}
               onNewRequest={() => setIntakeOpen(true)} />
      <div className="content">
        <TopBar
          crumbs={crumbs}
          onOpenCommandPalette={() => setCommandOpen(true)}
          actions={<>
            <Button variant="ghost" icon="BellIcon" />
            <Button variant="ghost" icon="QuestionIcon" />
            <Button variant="ghost" icon="SlidersIcon" onClick={() => setEditMode(prev => !prev)} title="Tweaks (⌘,)" />
            <Button variant="primary" size="sm" icon="PlusIcon" onClick={() => setIntakeOpen(true)}>New request</Button>
          </>}
        />
        <div className={`page ${isInbox ? 'page-inbox' : ''}`}>
          <div key={pageKey} className="page-transition" style={isInbox ? { height: '100%' } : undefined}>{page}</div>
        </div>
      </div>

      {intakeOpen && (
        <IntakeFormSlideOver
          onClose={() => setIntakeOpen(false)}
          onSubmit={handleAddTicket}
        />
      )}

      {editMode && (
        <div className="tweaks-panel">
          <div className="tweaks-head">
            <div className="t"><Icon name="SlidersIcon" size={14} />Tweaks</div>
            <Icon name="XIcon" size={14} style={{ cursor: 'pointer', color: 'var(--text-neutral-subtle)' }} onClick={() => setEditMode(false)} />
          </div>
          <div className="tweaks-body">
            <div className="tweak-row">
              <label>Theme</label>
              <div className="opts">
                {[{v: false, l: 'Light'}, {v: true, l: 'Dark'}].map(({v, l}) => (
                  <div key={l} className={`tweak-opt ${darkMode === v ? 'on' : ''}`} onClick={() => setDarkMode(v)}>{l}</div>
                ))}
              </div>
            </div>
            <div className="tweak-row">
              <label>Density</label>
              <div className="opts">
                {['comfortable', 'compact'].map(v => (
                  <div key={v} className={`tweak-opt ${tweaks.density === v ? 'on' : ''}`} onClick={() => setTweak('density', v)}>{v}</div>
                ))}
              </div>
            </div>
            <div className="tweak-row">
              <label>Inbox layout</label>
              <div className="opts">
                {[{id:'kanban',l:'Board'},{id:'split',l:'Split'},{id:'full-detail',l:'Full'},{id:'list-only',l:'List'}].map(v => (
                  <div key={v.id} className={`tweak-opt ${tweaks.layout === v.id ? 'on' : ''}`} onClick={() => setTweak('layout', v.id)}>{v.l}</div>
                ))}
              </div>
            </div>
            <div className="tweak-row">
              <label>Brand color</label>
              <div className="swatches">
                {BRAND_PRESETS.map(p => (
                  <div key={p.color} className={`swatch ${tweaks.brandColor === p.color ? 'on' : ''}`}
                       style={{ background: p.color }} title={p.name}
                       onClick={() => setTweak('brandColor', p.color)} />
                ))}
              </div>
            </div>
          </div>
        </div>
      )}

      <CommandPalette
        open={commandOpen}
        query={commandQuery}
        items={commandItems}
        activeIndex={commandIndex}
        onClose={() => {
          setCommandOpen(false);
          setCommandQuery('');
        }}
        onQueryChange={setCommandQuery}
        onSelect={runCommand}
        onHover={setCommandIndex}
      />
      <ToastContainer toasts={toasts} onRemoveToast={removeToast} />
    </div>
  );
}

Object.assign(window, { useCountUp });
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
