import { useDeferredValue, useEffect, useMemo, useState, useTransition } from 'react';
import Editor, { loader } from '@monaco-editor/react';
import * as monaco from 'monaco-editor';
import './monacoSetup';
import { getCatalog, harnessForModule, type CatalogEntry } from './catalog';
import { DEFAULT_SOURCE } from './defaultSource';
import { loadManualSession, type ManualSessionInput } from './liveClient';
import { PlaygroundProviders } from './mocks/PlaygroundProviders';
import { PreviewErrorBoundary } from './PreviewErrorBoundary';
import { ResizableStage } from './ResizableStage';
import { LivePreview } from './LivePreview';
import { usePlaygroundMatrix } from './usePlaygroundMatrix';

// Use the npm monaco build — the CDN AMD loader breaks UMD deps like sanitize-html.
loader.config({ monaco });

type EditorTab = 'component' | 'harness';

async function pushLiveSource(source: string) {
  await fetch('/__playground/live', {
    method: 'POST',
    headers: { 'Content-Type': 'text/plain; charset=utf-8' },
    body: source,
  });
}

async function fetchComponentSource(filePath: string): Promise<string> {
  const res = await fetch(`/__playground/source?path=${encodeURIComponent(filePath)}`);
  if (!res.ok) throw new Error(`Failed to load ${filePath}: ${res.status}`);
  return res.text();
}

async function pushComponentSource(filePath: string, source: string) {
  await fetch(`/__playground/source?path=${encodeURIComponent(filePath)}`, {
    method: 'POST',
    headers: { 'Content-Type': 'text/plain; charset=utf-8' },
    body: source,
  });
}

export function App() {
  const catalog = useMemo(() => getCatalog(), []);
  const [filter, setFilter] = useState('');
  const [harnessSource, setHarnessSource] = useState(DEFAULT_SOURCE);
  const [componentSource, setComponentSource] = useState<string>('');
  const [editorTab, setEditorTab] = useState<EditorTab>('harness');
  const [selected, setSelected] = useState<CatalogEntry | null>(null);
  const [loadError, setLoadError] = useState<string | null>(null);
  const [showTokenForm, setShowTokenForm] = useState(false);
  const [manual, setManual] = useState<ManualSessionInput>(() => {
    const saved = loadManualSession();
    return (
      saved ?? {
        baseUrl: 'https://matrix.org',
        userId: '@you:matrix.org',
        deviceId: 'PLAYGROUND',
        accessToken: '',
      }
    );
  });
  const deferredHarness = useDeferredValue(harnessSource);
  const deferredComponent = useDeferredValue(componentSource);
  const [, startTransition] = useTransition();
  const matrix = usePlaygroundMatrix();

  useEffect(() => {
    fetch('/__playground/live')
      .then((r) => r.text())
      .then((text) => {
        if (text.trim()) setHarnessSource(text);
      })
      .catch(() => undefined);
  }, []);

  useEffect(() => {
    const handle = window.setTimeout(() => {
      void pushLiveSource(deferredHarness);
    }, 250);
    return () => window.clearTimeout(handle);
  }, [deferredHarness]);

  // Upgrade stale RoomInput harness (bare <RoomInput /> or mocks.room) to useRoom().
  useEffect(() => {
    if (!selected?.importPath.endsWith('/RoomInput')) return;
    if (harnessSource.includes('useRoom()')) return;
    setHarnessSource(harnessForModule(selected.importPath));
  }, [selected, harnessSource]);

  // Writes the real component file — strings/markup edits land in src/app/…
  useEffect(() => {
    if (!selected || editorTab !== 'component') return;
    if (!deferredComponent) return;
    const handle = window.setTimeout(() => {
      void pushComponentSource(selected.filePath, deferredComponent).catch((err) => {
        setLoadError(err instanceof Error ? err.message : String(err));
      });
    }, 400);
    return () => window.clearTimeout(handle);
  }, [deferredComponent, editorTab, selected]);

  const filtered = useMemo(() => {
    const q = filter.trim().toLowerCase();
    if (!q) return catalog;
    return catalog.filter((e) => e.label.toLowerCase().includes(q));
  }, [catalog, filter]);

  const openEntry = (entry: CatalogEntry) => {
    setSelected(entry);
    setLoadError(null);
    setEditorTab('component');
    startTransition(() => {
      setHarnessSource(harnessForModule(entry.importPath));
    });
    void fetchComponentSource(entry.filePath)
      .then((text) => setComponentSource(text))
      .catch((err) => {
        setComponentSource('');
        setLoadError(err instanceof Error ? err.message : String(err));
      });
  };

  const liveReady = matrix.mode === 'live' && matrix.status === 'ready' && matrix.client;
  const previewClient = liveReady ? matrix.client! : undefined;
  const previewRoom = liveReady ? matrix.room ?? undefined : undefined;

  const editorValue = editorTab === 'component' ? componentSource : harnessSource;
  const editorPath =
    editorTab === 'component' && selected
      ? selected.filePath
      : 'playground-live.tsx';

  return (
    <div className="app">
      <header className="topbar">
        <div>
          <strong>Component Playground</strong>
          <span className="muted">
            {' '}
            — Component tab edits real source · Harness tab only mounts it
          </span>
        </div>
        <div className="topbar-actions">
          <button
            type="button"
            className="ghost"
            onClick={() => {
              setSelected(null);
              setComponentSource('');
              setEditorTab('harness');
              setHarnessSource(DEFAULT_SOURCE);
            }}
          >
            Reset message preview
          </button>
        </div>
      </header>

      <div className="conn-bar">
        <div className="conn-modes">
          <button
            type="button"
            className={matrix.mode === 'mock' ? 'conn-btn active' : 'conn-btn'}
            onClick={() => matrix.useMocks()}
          >
            Mocks
          </button>
          <button
            type="button"
            className={matrix.mode === 'live' ? 'conn-btn active' : 'conn-btn'}
            disabled={matrix.status === 'connecting'}
            onClick={() => {
              void matrix.connectLive();
            }}
            title={
              matrix.hasPaarrotSession
                ? `Use logged-in session ${matrix.paarrotUserId}`
                : 'Needs Paarrot login on this origin, or a pasted token'
            }
          >
            {matrix.status === 'connecting' ? 'Connecting…' : 'Paarrot session'}
          </button>
          <button
            type="button"
            className="conn-btn"
            onClick={() => setShowTokenForm((v) => !v)}
          >
            Token…
          </button>
        </div>

        <div className="conn-status">
          {matrix.mode === 'mock' && <span className="muted">Using lightweight mocks</span>}
          {matrix.mode === 'live' && matrix.status === 'connecting' && (
            <span className="muted">Syncing (memory store, no crypto)…</span>
          )}
          {matrix.mode === 'live' && matrix.status === 'ready' && (
            <span className="conn-ok">{matrix.sessionLabel}</span>
          )}
          {matrix.mode === 'live' && matrix.status === 'error' && (
            <span className="conn-err">{matrix.error}</span>
          )}
          {!matrix.hasPaarrotSession && matrix.mode !== 'live' && (
            <span className="muted">Tip: log into Paarrot at / first, then hit Paarrot session</span>
          )}
        </div>

        {liveReady && matrix.rooms.length > 0 && (
          <label className="conn-room">
            Room
            <select
              value={matrix.room?.roomId ?? ''}
              onChange={(e) => matrix.selectRoom(e.target.value)}
            >
              {matrix.rooms.map((r) => (
                <option key={r.roomId} value={r.roomId}>
                  {r.name || r.roomId}
                </option>
              ))}
            </select>
          </label>
        )}
      </div>

      {showTokenForm && (
        <form
          className="token-form"
          onSubmit={(e) => {
            e.preventDefault();
            setShowTokenForm(false);
            void matrix.connectLive(manual);
          }}
        >
          <input
            placeholder="Homeserver URL"
            value={manual.baseUrl}
            onChange={(e) => setManual((m) => ({ ...m, baseUrl: e.target.value }))}
          />
          <input
            placeholder="@user:server"
            value={manual.userId}
            onChange={(e) => setManual((m) => ({ ...m, userId: e.target.value }))}
          />
          <input
            placeholder="Device ID"
            value={manual.deviceId}
            onChange={(e) => setManual((m) => ({ ...m, deviceId: e.target.value }))}
          />
          <input
            placeholder="Access token"
            value={manual.accessToken}
            onChange={(e) => setManual((m) => ({ ...m, accessToken: e.target.value }))}
          />
          <button type="submit" className="ghost">
            Connect
          </button>
        </form>
      )}

      <div className="panes">
        <aside className="pane catalog-pane">
          <div className="pane-label">Cinny modules ({filtered.length})</div>
          <input
            className="filter"
            placeholder="Filter components…"
            value={filter}
            onChange={(e) => setFilter(e.target.value)}
          />
          <div className="catalog-list">
            {filtered.map((entry) => (
              <button
                key={entry.id}
                type="button"
                className={selected?.id === entry.id ? 'catalog-item active' : 'catalog-item'}
                onClick={() => openEntry(entry)}
                title={entry.filePath}
              >
                {entry.label}
              </button>
            ))}
          </div>
        </aside>

        <section className="pane editor-pane">
          <div className="pane-label editor-tabs">
            <button
              type="button"
              className={editorTab === 'component' ? 'tab active' : 'tab'}
              disabled={!selected}
              onClick={() => setEditorTab('component')}
              title={selected ? `Edit ${selected.filePath}` : 'Pick a module first'}
            >
              Component
            </button>
            <button
              type="button"
              className={editorTab === 'harness' ? 'tab active' : 'tab'}
              onClick={() => setEditorTab('harness')}
            >
              Harness
            </button>
            <span className="muted tab-hint">
              {editorTab === 'component' && selected
                ? `writes ${selected.filePath}`
                : 'mount-only · not saved'}
            </span>
          </div>
          {loadError && <pre className="error-block editor-error">{loadError}</pre>}
          <Editor
            height="100%"
            defaultLanguage="typescript"
            path={editorPath}
            theme="vs-dark"
            value={editorValue}
            onChange={(value) => {
              const next = value ?? '';
              if (editorTab === 'component') setComponentSource(next);
              else setHarnessSource(next);
            }}
            options={{
              minimap: { enabled: false },
              fontSize: 13,
              fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Consolas, monospace',
              scrollBeyondLastLine: false,
              wordWrap: 'on',
              tabSize: 2,
              automaticLayout: true,
              padding: { top: 12 },
              readOnly: editorTab === 'component' && !selected,
            }}
            beforeMount={(monaco) => {
              monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
                jsx: monaco.languages.typescript.JsxEmit.ReactJSX,
                target: monaco.languages.typescript.ScriptTarget.ES2020,
                allowNonTsExtensions: true,
                esModuleInterop: true,
                moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs,
                paths: {
                  '@cinny/*': ['*'],
                  '@playground/mocks': ['*'],
                },
              });
              monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({
                noSemanticValidation: true,
                noSyntaxValidation: false,
              });
            }}
          />
        </section>

        <section className="pane preview-pane">
          <div className="pane-label">Preview</div>
          <div className="preview-body">
            <ResizableStage>
              <PlaygroundProviders client={previewClient} room={previewRoom}>
                <PreviewErrorBoundary resetKey={`${deferredHarness}:${selected?.filePath ?? ''}`}>
                  <LivePreview />
                </PreviewErrorBoundary>
              </PlaygroundProviders>
            </ResizableStage>
          </div>
        </section>
      </div>
    </div>
  );
}
