/* Aplicación React (sin compilación: Babel standalone la transpila en el navegador). */
const { useState, useEffect, useRef, useCallback, useMemo } = React;

// ---------- Utilidades ----------
async function api(method, url, body) {
  const res = await fetch(url, {
    method,
    headers: body ? { 'Content-Type': 'application/json' } : undefined,
    body: body ? JSON.stringify(body) : undefined,
  });
  const data = await res.json().catch(() => ({}));
  if (!res.ok) throw new Error(data.error || `Error ${res.status}`);
  return data;
}

const boardIdFromUrl = () => {
  const m = location.pathname.match(/^\/board\/([0-9a-f-]{36})\/?$/i);
  return m ? m[1].toLowerCase() : null;
};

const parseTags = (s) =>
  (s || '')
    .split(',')
    .map((t) => t.trim())
    .filter(Boolean);

function cardMatches(card, term) {
  const t = term.trim().toLowerCase();
  if (!t) return true;
  return [card.title, card.description, ...card.tags].some((s) => s.toLowerCase().includes(t));
}

const inputCls = 'rounded-md border border-slate-300 bg-white px-2 py-1 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-400';
const btnCls = 'rounded-md bg-indigo-600 px-3 py-1 text-sm font-medium text-white hover:bg-indigo-700 disabled:opacity-50';
const iconBtn = 'rounded p-1 text-slate-400 hover:bg-slate-200 hover:text-slate-700';

// ---------- Autenticación humana ----------
function Home() {
  const [mode, setMode] = useState('login');
  const [error, setError] = useState('');
  const [busy, setBusy] = useState(true);

  useEffect(() => {
    api('GET', '/api/auth/me')
      .then((r) => {
        if (r.user) location.replace('/board/' + r.boardId);
        else setBusy(false);
      })
      .catch(() => setBusy(false));
  }, []);

  async function submit(e) {
    e.preventDefault();
    setError('');
    const f = new FormData(e.target);
    try {
      const r = await api('POST', `/api/auth/${mode}`, { username: f.get('username'), password: f.get('password') });
      location.replace('/board/' + r.boardId);
    } catch (err) {
      setError(err.message);
    }
  }

  if (busy) return <div className="p-8 text-slate-500">Cargando…</div>;
  return (
    <div className="flex min-h-screen items-center justify-center p-4">
      <form onSubmit={submit} className="w-full max-w-sm space-y-4 rounded-xl bg-white p-6 shadow-lg">
        <h1 className="text-2xl font-bold text-slate-800">Kanban</h1>
        <p className="text-sm text-slate-500">{mode === 'login' ? 'Inicia sesión para ver tu tablero.' : 'Crea una cuenta nueva.'}</p>
        <input name="username" required minLength={3} autoComplete="username" placeholder="Usuario" className={inputCls + ' w-full'} />
        <input
          name="password"
          type="password"
          required
          minLength={8}
          autoComplete={mode === 'login' ? 'current-password' : 'new-password'}
          placeholder="Contraseña (mín. 8 caracteres)"
          className={inputCls + ' w-full'}
        />
        {error && (
          <p role="alert" className="text-sm text-red-600">
            {error}
          </p>
        )}
        <button className={btnCls + ' w-full py-2'}>{mode === 'login' ? 'Entrar' : 'Registrarme'}</button>
        <button
          type="button"
          onClick={() => {
            setMode(mode === 'login' ? 'register' : 'login');
            setError('');
          }}
          className="w-full text-sm text-indigo-600 hover:underline"
        >
          {mode === 'login' ? '¿No tienes cuenta? Regístrate' : '¿Ya tienes cuenta? Inicia sesión'}
        </button>
      </form>
    </div>
  );
}

// ---------- Tablero ----------
function Board({ boardId }) {
  const [state, setState] = useState(null);
  const [filter, setFilter] = useState('');
  const [error, setError] = useState('');
  const [user, setUser] = useState(null);
  const [drag, setDrag] = useState(null);
  const stateRef = useRef(null);
  const filterRef = useRef('');
  stateRef.current = state;
  filterRef.current = filter;

  const base = `/api/boards/${boardId}`;

  // Toda mutación pasa por aquí: llama al API (SQLite) y sustituye el estado de React.
  const run = useCallback(
    async (method, path, body) => {
      try {
        const r = await api(method, base + path, body);
        setState(r.state);
        setError('');
        return r;
      } catch (e) {
        setError(e.message);
        throw e;
      }
    },
    [base]
  );

  // Carga inicial + sondeo para reflejar cambios hechos por otros clientes.
  useEffect(() => {
    let alive = true;
    const load = () =>
      api('GET', base)
        .then((s) => {
          if (alive && JSON.stringify(s) !== JSON.stringify(stateRef.current)) setState(s);
        })
        .catch((e) => alive && !stateRef.current && setError(e.message));
    load();
    const t = setInterval(load, 3000);
    return () => {
      alive = false;
      clearInterval(t);
    };
  }, [base]);

  useEffect(() => {
    api('GET', '/api/auth/me')
      .then((r) => setUser(r.user))
      .catch(() => {});
  }, []);

  // Acciones expuestas globalmente: las usan tanto la UI como las herramientas WebMCP.
  const actions = useMemo(
    () => ({
      getState: () => stateRef.current,
      createColumn: (title) => run('POST', '/columns', { title }),
      renameColumn: (columnId, newTitle) => run('PATCH', `/columns/${columnId}`, { title: newTitle }),
      deleteColumn: (columnId) => run('DELETE', `/columns/${columnId}`),
      reorderColumns: (columnIds) => run('PUT', '/columns/order', { columnIds }),
      createCard: (data) => run('POST', '/cards', data),
      updateCard: (cardId, data) => run('PATCH', `/cards/${cardId}`, data),
      deleteCard: (cardId) => run('DELETE', `/cards/${cardId}`),
      moveCard: (cardId, targetColumnId, position) => run('POST', `/cards/${cardId}/move`, { targetColumnId, position }),
      setFilter: (searchTerm) => {
        setFilter(searchTerm);
        const matches = (stateRef.current?.columns || []).reduce((n, c) => n + c.cards.filter((k) => cardMatches(k, searchTerm)).length, 0);
        return { searchTerm, matches };
      },
    }),
    [run]
  );

  useEffect(() => {
    if (state) window.kanbanApp = { ...actions, ready: true, boardId };
  }, [state, actions, boardId]);

  const swallow = (p) => p.catch(() => {});

  if (!state) {
    return <div className="p-8 text-slate-500">{error ? <span className="text-red-600">{error}</span> : 'Cargando tablero…'}</div>;
  }

  const columns = state.columns;
  const visibleCount = columns.reduce((n, c) => n + c.cards.filter((k) => cardMatches(k, filter)).length, 0);

  function moveColumn(index, delta) {
    const ids = columns.map((c) => c.id);
    const j = index + delta;
    if (j < 0 || j >= ids.length) return;
    [ids[index], ids[j]] = [ids[j], ids[index]];
    swallow(actions.reorderColumns(ids));
  }

  const submitForm = (fn) => (e) => {
    e.preventDefault();
    const form = e.target;
    const f = new FormData(form);
    Promise.resolve(fn(f)).then(
      () => form.reset(),
      () => {}
    );
  };

  return (
    <div className="flex min-h-screen flex-col">
      <header className="flex flex-wrap items-center gap-3 border-b bg-white px-4 py-3">
        <h1 className="text-lg font-bold text-slate-800">Kanban</h1>
        <span className="hidden truncate text-xs text-slate-400 sm:inline">/board/{boardId}</span>
        <div className="ml-auto flex items-center gap-3 text-sm">
          {user ? (
            <>
              <span className="text-slate-600">{user.username}</span>
              <button
                className="text-indigo-600 hover:underline"
                onClick={() => api('POST', '/api/auth/logout').then(() => location.replace('/'))}
              >
                Salir
              </button>
            </>
          ) : (
            <a className="text-indigo-600 hover:underline" href="/">
              Iniciar sesión
            </a>
          )}
        </div>
      </header>

      <section className="grid gap-3 border-b bg-slate-50 p-3 md:grid-cols-2 xl:grid-cols-4">
        <form
          onSubmit={(e) => {
            e.preventDefault();
            actions.setFilter(new FormData(e.target).get('searchTerm') || '');
          }}
          webmcp-tool="ui_filter_cards"
          webmcp-description="Filtra las tarjetas visibles por texto o etiqueta (solo visual, no modifica datos). Vacío quita el filtro."
          toolname="ui_filter_cards"
          tooldescription="Filtra las tarjetas visibles por texto o etiqueta (solo visual, no modifica datos). Vacío quita el filtro."
          className="flex items-center gap-2"
        >
          <input
            name="searchTerm"
            value={filter}
            onChange={(e) => setFilter(e.target.value)}
            placeholder="Filtrar por texto o etiqueta…"
            aria-label="Filtrar tarjetas"
            className={inputCls + ' flex-1'}
          />
          <span className="text-xs text-slate-500">{visibleCount} tarjetas</span>
        </form>

        <form
          onSubmit={submitForm((f) => actions.createColumn(f.get('title')))}
          webmcp-tool="ui_add_column"
          webmcp-description="Agrega una columna nueva al final del tablero Kanban."
          toolname="ui_add_column"
          tooldescription="Agrega una columna nueva al final del tablero Kanban."
          className="flex gap-2"
        >
          <input
            name="title"
            required
            maxLength={80}
            placeholder="Nueva columna"
            aria-label="Título de la nueva columna"
            className={inputCls + ' flex-1'}
          />
          <button className={btnCls}>Agregar</button>
        </form>

        <form
          onSubmit={submitForm((f) => actions.renameColumn(f.get('columnId'), f.get('newTitle')))}
          webmcp-tool="ui_rename_column"
          webmcp-description="Renombra una columna existente. columnId es el id de la columna; newTitle el nuevo título."
          toolname="ui_rename_column"
          tooldescription="Renombra una columna existente. columnId es el id de la columna; newTitle el nuevo título."
          className="flex gap-2"
        >
          <select name="columnId" required aria-label="Columna a renombrar" className={inputCls}>
            {columns.map((c) => (
              <option key={c.id} value={c.id}>
                {c.title}
              </option>
            ))}
          </select>
          <input
            name="newTitle"
            required
            maxLength={80}
            placeholder="Nuevo título"
            aria-label="Nuevo título"
            className={inputCls + ' min-w-0 flex-1'}
          />
          <button className={btnCls}>Renombrar</button>
        </form>

        <form
          onSubmit={submitForm((f) =>
            actions.createCard({
              columnId: f.get('columnId'),
              title: f.get('title'),
              description: f.get('description') || undefined,
              tags: parseTags(f.get('tags')),
            })
          )}
          webmcp-tool="ui_add_card"
          webmcp-description="Agrega una tarjeta al final de una columna. columnId (id de columna), title, description y tags (separadas por comas) opcionales."
          toolname="ui_add_card"
          tooldescription="Agrega una tarjeta al final de una columna. columnId (id de columna), title, description y tags (separadas por comas) opcionales."
          className="flex flex-wrap gap-2"
        >
          <select name="columnId" required aria-label="Columna de la tarjeta" className={inputCls}>
            {columns.map((c) => (
              <option key={c.id} value={c.id}>
                {c.title}
              </option>
            ))}
          </select>
          <input
            name="title"
            required
            maxLength={200}
            placeholder="Título de tarjeta"
            aria-label="Título de la tarjeta"
            className={inputCls + ' min-w-0 flex-1'}
          />
          <input
            name="description"
            maxLength={5000}
            placeholder="Descripción"
            aria-label="Descripción"
            className={inputCls + ' min-w-0 flex-1'}
          />
          <input
            name="tags"
            placeholder="etiquetas, separadas, por comas"
            aria-label="Etiquetas"
            className={inputCls + ' min-w-0 flex-1'}
          />
          <button className={btnCls} disabled={!columns.length}>
            Agregar
          </button>
        </form>
      </section>

      {error && (
        <div role="alert" className="bg-red-50 px-4 py-2 text-sm text-red-700">
          {error}
        </div>
      )}

      <main className="flex flex-1 items-start gap-4 overflow-x-auto p-4">
        {columns.length === 0 && <p className="text-slate-500">No hay columnas. Agrega la primera arriba.</p>}
        {columns.map((col, i) => (
          <Column
            key={col.id}
            col={col}
            index={i}
            total={columns.length}
            filter={filter}
            drag={drag}
            setDrag={setDrag}
            actions={actions}
            onMove={moveColumn}
            swallow={swallow}
          />
        ))}
      </main>
    </div>
  );
}

function Column({ col, index, total, filter, drag, setDrag, actions, onMove, swallow }) {
  const [editing, setEditing] = useState(false);
  const [over, setOver] = useState(false);
  const cards = col.cards.filter((k) => cardMatches(k, filter));

  const drop = (position) => {
    if (drag) swallow(actions.moveCard(drag, col.id, position));
    setDrag(null);
    setOver(false);
  };

  return (
    <div
      className={'flex w-72 shrink-0 flex-col rounded-xl bg-slate-200/70 p-2 ' + (over ? 'ring-2 ring-indigo-400' : '')}
      onDragOver={(e) => {
        if (drag) {
          e.preventDefault();
          setOver(true);
        }
      }}
      onDragLeave={(e) => {
        if (!e.currentTarget.contains(e.relatedTarget)) setOver(false);
      }}
      onDrop={(e) => {
        e.preventDefault();
        drop(col.cards.length);
      }}
    >
      <div className="mb-2 flex items-center gap-1 px-1">
        {editing ? (
          <form
            className="flex-1"
            onSubmit={(e) => {
              e.preventDefault();
              const v = new FormData(e.target).get('title');
              setEditing(false);
              if (v.trim() && v !== col.title) swallow(actions.renameColumn(col.id, v));
            }}
          >
            <input
              name="title"
              autoFocus
              defaultValue={col.title}
              maxLength={80}
              onBlur={(e) => e.target.form.requestSubmit()}
              className={inputCls + ' w-full'}
            />
          </form>
        ) : (
          <h2
            className="flex-1 cursor-text truncate font-semibold text-slate-700"
            title="Doble clic para renombrar"
            onDoubleClick={() => setEditing(true)}
          >
            {col.title}{' '}
            <span className="text-xs font-normal text-slate-500">
              ({filter ? `${cards.length}/` : ''}
              {col.cards.length})
            </span>
          </h2>
        )}
        <button
          className={iconBtn}
          disabled={index === 0}
          onClick={() => onMove(index, -1)}
          aria-label="Mover columna a la izquierda"
          title="Mover a la izquierda"
        >
          ◀
        </button>
        <button
          className={iconBtn}
          disabled={index === total - 1}
          onClick={() => onMove(index, 1)}
          aria-label="Mover columna a la derecha"
          title="Mover a la derecha"
        >
          ▶
        </button>
        <button
          className={iconBtn}
          aria-label="Eliminar columna"
          title="Eliminar columna"
          onClick={() => {
            if (confirm(`¿Eliminar la columna "${col.title}" y sus ${col.cards.length} tarjetas?`)) swallow(actions.deleteColumn(col.id));
          }}
        >
          🗑
        </button>
      </div>

      <div className="flex min-h-[2rem] flex-col gap-2">
        {cards.map((card) => (
          <Card
            key={card.id}
            card={card}
            actions={actions}
            swallow={swallow}
            setDrag={setDrag}
            onDropOn={() => drop(col.cards.findIndex((k) => k.id === card.id))}
          />
        ))}
      </div>
    </div>
  );
}

function Card({ card, actions, swallow, setDrag, onDropOn }) {
  const [editing, setEditing] = useState(false);

  if (editing) {
    return (
      <form
        className="space-y-2 rounded-lg bg-white p-2 shadow"
        onSubmit={(e) => {
          e.preventDefault();
          const f = new FormData(e.target);
          swallow(
            actions.updateCard(card.id, { title: f.get('title'), description: f.get('description'), tags: parseTags(f.get('tags')) })
          );
          setEditing(false);
        }}
      >
        <input name="title" required defaultValue={card.title} maxLength={200} aria-label="Título" className={inputCls + ' w-full'} />
        <textarea
          name="description"
          defaultValue={card.description}
          maxLength={5000}
          rows={3}
          aria-label="Descripción"
          className={inputCls + ' w-full'}
        />
        <input
          name="tags"
          defaultValue={card.tags.join(', ')}
          placeholder="etiquetas"
          aria-label="Etiquetas"
          className={inputCls + ' w-full'}
        />
        <div className="flex gap-2">
          <button className={btnCls}>Guardar</button>
          <button type="button" className="text-sm text-slate-500 hover:underline" onClick={() => setEditing(false)}>
            Cancelar
          </button>
        </div>
      </form>
    );
  }

  return (
    <div
      draggable
      onDragStart={(e) => {
        e.dataTransfer.setData('text/plain', card.id);
        e.dataTransfer.effectAllowed = 'move';
        setDrag(card.id);
      }}
      onDragEnd={() => setDrag(null)}
      onDragOver={(e) => e.preventDefault()}
      onDrop={(e) => {
        e.preventDefault();
        e.stopPropagation();
        onDropOn();
      }}
      className="group cursor-grab rounded-lg bg-white p-3 shadow-sm hover:shadow"
    >
      <div className="flex items-start gap-1">
        <h3 className="flex-1 break-words text-sm font-medium text-slate-800">{card.title}</h3>
        <button
          className={iconBtn + ' opacity-0 group-hover:opacity-100 focus:opacity-100'}
          aria-label="Editar tarjeta"
          onClick={() => setEditing(true)}
        >
          ✎
        </button>
        <button
          className={iconBtn + ' opacity-0 group-hover:opacity-100 focus:opacity-100'}
          aria-label="Eliminar tarjeta"
          onClick={() => {
            if (confirm(`¿Eliminar la tarjeta "${card.title}"?`)) swallow(actions.deleteCard(card.id));
          }}
        >
          ✕
        </button>
      </div>
      {card.description && <p className="mt-1 whitespace-pre-wrap break-words text-xs text-slate-600">{card.description}</p>}
      {card.tags.length > 0 && (
        <div className="mt-2 flex flex-wrap gap-1">
          {card.tags.map((t) => (
            <span key={t} className="rounded-full bg-indigo-100 px-2 py-0.5 text-xs text-indigo-700">
              {t}
            </span>
          ))}
        </div>
      )}
    </div>
  );
}

// ---------- Widget WebMCP ----------
function WebMCPBadge() {
  const [open, setOpen] = useState(false);
  const [info, setInfo] = useState({ api: false, tools: 0, total: 0, errors: [] });

  const refresh = useCallback(() => {
    const reg = window.webmcp || { tools: [], errors: [] };
    setInfo({
      api: !!((navigator.modelContext || document.modelContext) && (navigator.modelContext || document.modelContext).registerTool),
      tools: reg.tools.length,
      errors: reg.errors.slice(),
    });
  }, []);

  useEffect(() => {
    refresh();
    window.addEventListener('webmcp:registered', refresh);
    return () => window.removeEventListener('webmcp:registered', refresh);
  }, [refresh]);

  const boardUrl = boardIdFromUrl() ? location.origin + location.pathname : null;
  const copy = (text) => navigator.clipboard && navigator.clipboard.writeText(text).catch(() => {});
  const ok = info.api && info.tools > 0;

  return (
    <>
      <button
        onClick={() => {
          refresh();
          setOpen(true);
        }}
        className="fixed bottom-4 right-4 z-40 rounded-full bg-slate-800/90 px-3 py-1.5 text-xs text-white shadow-lg hover:bg-slate-700"
      >
        WebMCP Habilitado 🤖
      </button>
      {open && (
        <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4" onClick={() => setOpen(false)}>
          <div
            role="dialog"
            aria-modal="true"
            aria-label="WebMCP"
            className="w-full max-w-lg space-y-4 rounded-xl bg-white p-5 shadow-xl"
            onClick={(e) => e.stopPropagation()}
          >
            <div className="flex items-center">
              <h2 className="flex-1 text-lg font-bold text-slate-800">WebMCP 🤖</h2>
              <button className={iconBtn} onClick={() => setOpen(false)} aria-label="Cerrar">
                ✕
              </button>
            </div>

            <ul className="space-y-1 text-sm">
              <li>
                {info.api ? '🟢' : '🔴'} API <code>navigator.modelContext</code>: {info.api ? 'inicializada' : 'no disponible'}
              </li>
              <li>
                {ok ? '🟢' : '🟡'} Herramientas registradas: <b>{info.tools}</b>
              </li>
              {info.errors.map((e, i) => (
                <li key={i} className="text-red-600">
                  ⚠ {e}
                </li>
              ))}
            </ul>

            <div className="space-y-2 text-sm text-slate-700">
              <p className="font-medium">Conectar un agente externo mediante el puente local:</p>
              <ol className="list-decimal space-y-2 pl-5">
                <li>
                  Configura el relay como servidor MCP en tu cliente (Claude Desktop, Cursor, Claude Code…); el cliente lo ejecuta con:
                  <div className="mt-1 flex items-center gap-2 rounded bg-slate-100 p-2 font-mono text-xs">
                    <span className="flex-1">npx -y @mcp-b/webmcp-local-relay@latest</span>
                    <button className="text-indigo-600 hover:underline" onClick={() => copy('npx -y @mcp-b/webmcp-local-relay@latest')}>
                      Copiar
                    </button>
                  </div>
                </li>
                <li>
                  Mantén esta pestaña abierta: esta página ya incluye el script del relay y se conecta por WebSocket a{' '}
                  <code>ws://localhost:9333</code> (el relay debe estar en la misma máquina que el navegador).
                </li>
                <li>
                  Pide al agente que use <code>webmcp_list_sources</code> para ver esta pestaña y luego <code>get_kanban_state</code>.
                </li>
              </ol>
              <p className="text-xs text-slate-500">
                El estado del relay no es observable desde la página; el indicador verde confirma que las herramientas están registradas. Si
                el agente no ve la pestaña, reinicia el cliente MCP y recarga la página.
              </p>
            </div>

            <div className="space-y-1 text-sm">
              <p className="font-medium text-slate-700">Documentación para agentes</p>
              <a className="text-indigo-600 hover:underline" href="/llms.txt" target="_blank">
                /llms.txt
              </a>
              {' · '}
              <a className="text-indigo-600 hover:underline" href="/llms-full.txt" target="_blank">
                /llms-full.txt
              </a>
              {boardUrl && (
                <button className="ml-3 text-indigo-600 hover:underline" onClick={() => copy(boardUrl)}>
                  Copiar URL de este tablero
                </button>
              )}
            </div>
            <button className="text-sm text-slate-500 hover:underline" onClick={refresh}>
              Verificar de nuevo
            </button>
          </div>
        </div>
      )}
    </>
  );
}

function App() {
  const boardId = boardIdFromUrl();
  return (
    <>
      {boardId ? <Board boardId={boardId} /> : <Home />}
      <WebMCPBadge />
    </>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
