'use client';

/**
 * Connectors marketplace — lives in SkillsPanel's Connectors tab.
 *
 * Lists everything in the `forge-connectors` registry merged with the
 * user's installed state. Two-pane layout (list + detail) mirroring
 * the Skills tab:
 *   - Left: registry entries with status chip + Install/Update/Remove
 *   - Right: tools, description, settings form (PAT, base URL, …)
 *
 * Talks to:
 *   GET  /api/connectors/marketplace
 *   POST /api/connectors/marketplace { action, id, refreshInstalled? }
 *   GET  /api/connectors/<id>/settings
 *   POST /api/connectors/<id>/settings
 */

import { useCallback, useEffect, useMemo, useRef, useState } from 'react';

interface MarketEntry {
  id: string;
  name: string;
  version: string;
  icon?: string;
  description?: string;
  author?: string;
  installed_version?: string;
  update_available?: boolean;
  compatible: boolean;
  source: 'registry' | 'local';
}

interface MarketState {
  fetched_at?: string;
  base_url?: string;
  entries: MarketEntry[];
}

interface FieldSchema {
  type: string;
  label?: string;
  description?: string;
  required?: boolean;
  default?: any;
  options?: string[];
}

interface ConnectorTool {
  description?: string;
  destructive?: boolean;
  returns?: string;
  protocol?: string;
}

interface ConnectorEntryShape {
  id: string;
  tools?: Record<string, ConnectorTool>;
}

interface ConnectorDetail {
  plugin_id: string;
  name: string;
  icon: string;
  version: string;
  description?: string;
  author?: string;
  installed: boolean;
  entries: ConnectorEntryShape[];
  has_test?: boolean;
  test_description?: string;
}

interface TestResult {
  ok: boolean;
  status?: number;
  message?: string;
  error?: string;
  duration_ms?: number;
}

const SECRET_MASK = '••••••••';

export default function ConnectorsPanel() {
  const [state, setState] = useState<MarketState | null>(null);
  const [busyId, setBusyId] = useState('');
  const [syncing, setSyncing] = useState(false);
  const [error, setError] = useState('');

  const [selectedId, setSelectedId] = useState<string | null>(null);
  const [detail, setDetail] = useState<ConnectorDetail | null>(null);
  const [schema, setSchema] = useState<Record<string, FieldSchema>>({});
  const [values, setValues] = useState<Record<string, any>>({});
  const [savedAt, setSavedAt] = useState<number | null>(null);

  const [repoUrl, setRepoUrl] = useState<string>('');
  const [repoUrlDirty, setRepoUrlDirty] = useState(false);

  const [uploading, setUploading] = useState(false);
  const [dragOver, setDragOver] = useState(false);
  const fileInputRef = useRef<HTMLInputElement>(null);

  const [testing, setTesting] = useState(false);
  const [testResult, setTestResult] = useState<TestResult | null>(null);

  // ─── Load ─────────────────────────────────────────────────
  const refresh = useCallback(async () => {
    try {
      const r = await fetch('/api/connectors/marketplace');
      setState((await r.json()) as MarketState);
    } catch (e) {
      setError(e instanceof Error ? e.message : String(e));
    }
  }, []);

  const loadDetail = useCallback(async (id: string) => {
    setSavedAt(null);
    try {
      const [d, s] = await Promise.all([
        fetch(`/api/connectors?id=${id}`).then(r => r.ok ? r.json() : null),
        fetch(`/api/connectors/${id}/settings`).then(r => r.ok ? r.json() : null),
      ]);
      setDetail(d?.connector || null);
      setSchema(s?.schema || {});
      setValues(s?.settings || {});
    } catch {
      setDetail(null);
      setSchema({});
      setValues({});
    }
  }, []);

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

  // Auto-select first entry on first load (nice default)
  useEffect(() => {
    if (!selectedId && state?.entries?.length) {
      setSelectedId(state.entries[0].id);
    }
  }, [state, selectedId]);

  useEffect(() => {
    if (selectedId) loadDetail(selectedId);
    setTestResult(null);
  }, [selectedId, loadDetail]);

  // Keep the repo-url input in sync with whatever's in settings
  useEffect(() => {
    fetch('/api/settings').then(r => r.json()).then(s => {
      if (s?.connectorsRepoUrl != null) setRepoUrl(s.connectorsRepoUrl);
    }).catch(() => {});
  }, []);

  // ─── Actions ──────────────────────────────────────────────
  async function sync() {
    setSyncing(true); setError('');
    try {
      // Persist URL change first if dirty
      if (repoUrlDirty) {
        await fetch('/api/settings', {
          method: 'PUT',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ connectorsRepoUrl: repoUrl }),
        });
        setRepoUrlDirty(false);
      }
      const r = await fetch('/api/connectors/marketplace', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ action: 'sync', refreshInstalled: true }),
      });
      const j = await r.json();
      if (!j.ok) setError(j.error || 'sync failed');
      await refresh();
      if (selectedId) await loadDetail(selectedId);
    } catch (e) {
      setError(e instanceof Error ? e.message : String(e));
    } finally { setSyncing(false); }
  }

  async function act(action: 'install' | 'update' | 'uninstall', id: string) {
    if (action === 'uninstall' && !confirm(`Uninstall ${id}? Your saved settings/PAT are kept and restored on re-install.`)) return;
    setBusyId(id); setError('');
    try {
      const r = await fetch('/api/connectors/marketplace', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ action, id }),
      });
      const j = await r.json();
      if (!r.ok || j.ok === false) setError(j.error || `${action} failed`);
      await refresh();
      if (selectedId === id) await loadDetail(id);
    } catch (e) {
      setError(e instanceof Error ? e.message : String(e));
    } finally { setBusyId(''); }
  }

  async function uploadFile(file: File) {
    setUploading(true); setError('');
    try {
      const fd = new FormData();
      fd.append('file', file);
      const r = await fetch('/api/connectors/install-local', { method: 'POST', body: fd });
      const j = await r.json();
      if (!r.ok || j.ok === false) {
        setError(j.error || `install failed (HTTP ${r.status})`);
      } else {
        await refresh();
        if (j.id) setSelectedId(j.id);
      }
    } catch (e) {
      setError(e instanceof Error ? e.message : String(e));
    } finally { setUploading(false); }
  }

  function onPickFile(ev: React.ChangeEvent<HTMLInputElement>) {
    const f = ev.target.files?.[0];
    ev.target.value = ''; // allow re-picking the same file
    if (f) uploadFile(f);
  }

  function onDrop(ev: React.DragEvent) {
    ev.preventDefault();
    setDragOver(false);
    const f = ev.dataTransfer.files?.[0];
    if (f) uploadFile(f);
  }

  async function runTest() {
    if (!selectedId) return;
    setTesting(true); setTestResult(null);
    try {
      // Save first so the server-side test sees the current values
      // (most common gotcha: user pastes a new PAT, clicks Test, gets 401
      //  on the old token still on disk).
      await fetch(`/api/connectors/${selectedId}/settings`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ settings: values }),
      });
      const r = await fetch(`/api/connectors/${selectedId}/test`, { method: 'POST' });
      const j = (await r.json()) as TestResult;
      setTestResult(j);
    } catch (e) {
      setTestResult({ ok: false, error: e instanceof Error ? e.message : String(e) });
    } finally { setTesting(false); }
  }

  async function saveSettings() {
    if (!selectedId) return;
    try {
      const r = await fetch(`/api/connectors/${selectedId}/settings`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ settings: values }),
      });
      if (!r.ok) {
        const j = await r.json().catch(() => ({}));
        setError(j.error || 'save failed');
        return;
      }
      const j = await r.json();
      setValues(j.settings || values);
      setSavedAt(Date.now());
    } catch (e) {
      setError(e instanceof Error ? e.message : String(e));
    }
  }

  const entries = state?.entries || [];
  const installedCount = entries.filter(e => e.installed_version).length;
  const syncedLabel = state?.fetched_at
    ? new Date(state.fetched_at).toLocaleString()
    : 'never';

  const allTools = useMemo(() => {
    if (!detail) return [] as Array<[string, ConnectorTool]>;
    return detail.entries.flatMap(e => Object.entries(e.tools || {}));
  }, [detail]);

  return (
    <div
      className={`flex flex-1 min-h-0 flex-col relative ${dragOver ? 'ring-2 ring-[var(--accent)]/60' : ''}`}
      onDragOver={(e) => { e.preventDefault(); setDragOver(true); }}
      onDragLeave={() => setDragOver(false)}
      onDrop={onDrop}
    >
      {/* Header strip */}
      <div className="px-3 py-2 border-b border-[var(--border)] flex items-center gap-2 shrink-0">
        <span className="text-[10px] text-[var(--text-secondary)]">
          {installedCount} installed of {entries.length}
        </span>
        <span className="text-[10px] text-[var(--text-secondary)] flex-1 truncate">
          · Last synced: {syncedLabel}
        </span>
        <input
          value={repoUrl}
          onChange={e => { setRepoUrl(e.target.value); setRepoUrlDirty(true); }}
          placeholder="connectors registry URL"
          className="w-[300px] text-[10px] bg-[var(--bg-tertiary)] border border-[var(--border)] rounded px-2 py-0.5 text-[var(--text-primary)] font-mono focus:outline-none focus:border-[var(--accent)]"
        />
        <input
          ref={fileInputRef}
          type="file"
          accept=".yaml,.yml,.zip"
          onChange={onPickFile}
          className="hidden"
        />
        <button
          type="button"
          onClick={() => fileInputRef.current?.click()}
          disabled={uploading}
          title="Install a local connector — .yaml manifest or .zip bundle (drag & drop also works)"
          className="text-[10px] px-2.5 py-0.5 border border-[var(--border)] text-[var(--text-secondary)] rounded hover:border-[var(--accent)] hover:text-[var(--accent)] transition-colors disabled:opacity-40"
        >
          {uploading ? 'Installing…' : '+ Upload'}
        </button>
        <button
          onClick={sync}
          disabled={syncing}
          className="text-[10px] px-2.5 py-0.5 border border-[var(--accent)] text-[var(--accent)] rounded hover:bg-[var(--accent)] hover:text-white transition-colors disabled:opacity-40"
        >
          {syncing ? 'Syncing…' : 'Refresh'}
        </button>
      </div>

      {dragOver && (
        <div className="absolute inset-0 bg-[var(--accent)]/10 pointer-events-none flex items-center justify-center z-10">
          <div className="px-6 py-3 border-2 border-dashed border-[var(--accent)] bg-[var(--bg-primary)] rounded text-sm text-[var(--accent)]">
            Drop manifest.yaml or connector.zip to install
          </div>
        </div>
      )}

      {error && (
        <div className="mx-3 mt-2 px-3 py-1.5 text-[11px] text-red-400 border border-red-500/30 bg-red-500/5 rounded">
          {error}
        </div>
      )}

      <div className="flex flex-1 min-h-0">
        {/* List pane */}
        <div className="w-[280px] border-r border-[var(--border)] overflow-y-auto">
          {entries.length === 0 ? (
            <div className="p-4 text-xs text-[var(--text-secondary)] text-center">
              {state ? 'Registry is empty — click Refresh.' : 'Loading…'}
            </div>
          ) : (
            entries.map(e => {
              const installed = !!e.installed_version;
              const update = !!e.update_available;
              return (
                <button
                  key={e.id}
                  onClick={() => setSelectedId(e.id)}
                  className={`w-full text-left px-3 py-2 border-b border-[var(--border)] hover:bg-[var(--bg-tertiary)] transition-colors ${selectedId === e.id ? 'bg-[var(--bg-tertiary)]' : ''}`}
                >
                  <div className="flex items-center gap-2">
                    <span className="text-base">{e.icon || '🔌'}</span>
                    <div className="flex-1 min-w-0">
                      <div className="flex items-center gap-1.5">
                        <span className="text-[12px] font-semibold text-[var(--text-primary)] truncate">{e.name}</span>
                        {installed && !update && (
                          <span className="text-[8px] px-1 py-0.5 rounded bg-green-500/10 text-green-400">installed</span>
                        )}
                        {update && (
                          <span className="text-[8px] px-1 py-0.5 rounded bg-yellow-500/10 text-yellow-400">update</span>
                        )}
                        {e.source === 'local' && (
                          <span className="text-[8px] px-1 py-0.5 rounded bg-[var(--accent)]/15 text-[var(--accent)]">local</span>
                        )}
                      </div>
                      <div className="flex items-center gap-1.5 mt-0.5">
                        <span className="text-[9px] text-[var(--text-secondary)]">
                          v{e.version}
                          {installed && update && (
                            <span className="text-yellow-400"> ← v{e.installed_version}</span>
                          )}
                        </span>
                      </div>
                    </div>
                  </div>
                  {e.description && (
                    <p className="text-[10px] text-[var(--text-secondary)] mt-1 line-clamp-2">{e.description}</p>
                  )}
                </button>
              );
            })
          )}
        </div>

        {/* Detail pane */}
        <div className="flex-1 overflow-y-auto min-h-0">
          {!selectedId ? (
            <div className="h-full flex items-center justify-center text-xs text-[var(--text-secondary)]">
              Select a connector
            </div>
          ) : (() => {
            const me = entries.find(e => e.id === selectedId);
            if (!me) return null;
            const installed = !!me.installed_version;
            const update = !!me.update_available;
            return (
              <div className="p-4 space-y-4">
                {/* Header */}
                <div className="flex items-start gap-3">
                  <span className="text-2xl">{me.icon || '🔌'}</span>
                  <div className="flex-1 min-w-0">
                    <div className="flex items-center gap-2 flex-wrap">
                      <h2 className="text-base font-semibold text-[var(--text-primary)]">{me.name}</h2>
                      <span className="text-[10px] text-[var(--text-secondary)]">v{me.version}</span>
                      {installed && !update && (
                        <span className="text-[9px] px-1.5 py-0.5 rounded bg-green-500/10 text-green-400">installed</span>
                      )}
                      {update && (
                        <span className="text-[9px] px-1.5 py-0.5 rounded bg-yellow-500/10 text-yellow-400">
                          update available · v{me.installed_version} → v{me.version}
                        </span>
                      )}
                      {me.source === 'local' && (
                        <span className="text-[9px] px-1.5 py-0.5 rounded bg-[var(--accent)]/15 text-[var(--accent)]">
                          local · not in registry
                        </span>
                      )}
                    </div>
                    {me.description && (
                      <p className="text-[11px] text-[var(--text-secondary)] mt-1 whitespace-pre-line">{me.description}</p>
                    )}
                  </div>
                  <div className="flex items-center gap-1.5 shrink-0">
                    {!installed && (
                      <button
                        onClick={() => act('install', me.id)}
                        disabled={busyId === me.id || !me.compatible}
                        className="text-[10px] px-2.5 py-1 rounded border border-[var(--accent)] text-[var(--accent)] hover:bg-[var(--accent)] hover:text-white transition-colors disabled:opacity-40"
                      >
                        {busyId === me.id ? '…' : 'Install'}
                      </button>
                    )}
                    {installed && update && (
                      <button
                        onClick={() => act('update', me.id)}
                        disabled={busyId === me.id}
                        className="text-[10px] px-2.5 py-1 rounded border border-yellow-500/60 text-yellow-400 hover:bg-yellow-500 hover:text-white transition-colors disabled:opacity-40"
                      >
                        {busyId === me.id ? '…' : 'Update'}
                      </button>
                    )}
                    {installed && (
                      <button
                        onClick={() => act('uninstall', me.id)}
                        disabled={busyId === me.id}
                        className="text-[10px] px-2.5 py-1 rounded border border-red-500/30 text-red-400 hover:bg-red-500/10 transition-colors disabled:opacity-40"
                      >
                        Remove
                      </button>
                    )}
                  </div>
                </div>

                {/* Tools — only when manifest is on disk */}
                {installed && detail && (
                  <div>
                    <div className="flex items-center justify-between mb-1.5">
                      <h3 className="text-[11px] font-semibold text-[var(--text-primary)]">
                        Tools ({allTools.length})
                      </h3>
                      <span className="text-[9px] text-[var(--text-secondary)] italic">runtime: browser / http / shell</span>
                    </div>
                    <div className="grid gap-1.5">
                      {allTools.map(([name, tool]) => (
                        <div key={name} className="px-2.5 py-1.5 rounded bg-[var(--bg-tertiary)]">
                          <div className="flex items-center gap-2 mb-0.5">
                            <span className="text-[10px] font-mono font-semibold text-[var(--accent)]">{name}</span>
                            {tool.protocol && tool.protocol !== 'browser' && (
                              <span className="text-[8px] px-1 py-0.5 rounded bg-[var(--bg-secondary)] text-[var(--text-secondary)]">{tool.protocol}</span>
                            )}
                            {tool.destructive && (
                              <span className="text-[8px] px-1 py-0.5 rounded bg-red-500/10 text-red-400">destructive</span>
                            )}
                          </div>
                          {tool.description && (
                            <p className="text-[10px] text-[var(--text-secondary)] whitespace-pre-line">{tool.description.trim()}</p>
                          )}
                          {tool.returns && (
                            <p className="text-[9px] text-[var(--text-secondary)] mt-1 font-mono">→ {tool.returns}</p>
                          )}
                        </div>
                      ))}
                    </div>
                  </div>
                )}

                {/* Settings — only when manifest is on disk */}
                {installed && Object.keys(schema).length > 0 && (
                  <div>
                    <div className="flex items-center justify-between mb-1.5">
                      <h3 className="text-[11px] font-semibold text-[var(--text-primary)]">Settings</h3>
                      {savedAt && (
                        <span className="text-[9px] text-green-400">saved {new Date(savedAt).toLocaleTimeString()}</span>
                      )}
                    </div>
                    <div className="space-y-2">
                      {Object.entries(schema).map(([key, sc]) => (
                        <div key={key}>
                          <label className="text-[10px] text-[var(--text-secondary)] block mb-0.5">
                            {sc.label || key} {sc.required && <span className="text-red-400">*</span>}
                          </label>
                          {sc.type === 'boolean' ? (
                            <input
                              type="checkbox"
                              checked={values[key] === true || values[key] === 'true'}
                              onChange={e => setValues({ ...values, [key]: e.target.checked })}
                              className="accent-[var(--accent)]"
                            />
                          ) : sc.type === 'select' ? (
                            <select
                              value={values[key] ?? sc.default ?? ''}
                              onChange={e => setValues({ ...values, [key]: e.target.value })}
                              className="w-full bg-[var(--bg-tertiary)] border border-[var(--border)] rounded px-2 py-1 text-[11px] text-[var(--text-primary)]"
                            >
                              <option value="">Select…</option>
                              {(sc.options || []).map(o => <option key={o} value={o}>{o}</option>)}
                            </select>
                          ) : (
                            <input
                              type={sc.type === 'secret' || sc.type === 'password' ? 'password' : sc.type === 'number' ? 'number' : 'text'}
                              value={values[key] ?? sc.default ?? ''}
                              onChange={e => setValues({ ...values, [key]: e.target.value })}
                              placeholder={
                                (sc.type === 'secret' || sc.type === 'password') && values[key] === SECRET_MASK
                                  ? 'click to replace'
                                  : sc.description || ''
                              }
                              className="w-full bg-[var(--bg-tertiary)] border border-[var(--border)] rounded px-2 py-1 text-[11px] text-[var(--text-primary)] font-mono"
                            />
                          )}
                          {sc.description && (
                            <p className="text-[9px] text-[var(--text-secondary)] mt-0.5">{sc.description}</p>
                          )}
                        </div>
                      ))}
                      <div className="flex items-center gap-2 flex-wrap">
                        <button
                          onClick={saveSettings}
                          className="text-[10px] px-2.5 py-1 rounded border border-[var(--accent)] text-[var(--accent)] hover:bg-[var(--accent)] hover:text-white transition-colors"
                        >
                          Save settings
                        </button>
                        {detail?.has_test && (
                          <button
                            onClick={runTest}
                            disabled={testing}
                            title={detail.test_description || 'Verify credentials reach the service'}
                            className="text-[10px] px-2.5 py-1 rounded border border-[var(--border)] text-[var(--text-secondary)] hover:border-[var(--accent)] hover:text-[var(--accent)] transition-colors disabled:opacity-40"
                          >
                            {testing ? 'Testing…' : 'Test'}
                          </button>
                        )}
                        {testResult && (
                          <span
                            className={`text-[10px] ${
                              testResult.ok ? 'text-green-400' : 'text-red-400'
                            }`}
                          >
                            {testResult.ok
                              ? `✓ ${testResult.message}${testResult.duration_ms != null ? ` · ${testResult.duration_ms}ms` : ''}`
                              : `✗ ${testResult.error || `HTTP ${testResult.status}`}`}
                          </span>
                        )}
                      </div>
                    </div>
                  </div>
                )}

                {/* Not-installed hint */}
                {!installed && (
                  <p className="text-[11px] text-[var(--text-secondary)] italic">
                    Install this connector to see its tools and configure its settings (PAT, host URL, etc.).
                  </p>
                )}
              </div>
            );
          })()}
        </div>
      </div>
    </div>
  );
}
