<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Task Board</title>

  <script src="https://cdn.tailwindcss.com"></script>
  <script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>

  <style>
    body {
      background:
        radial-gradient(circle at top left, rgba(99,102,241,.25), transparent 32%),
        radial-gradient(circle at bottom right, rgba(14,165,233,.22), transparent 30%),
        linear-gradient(135deg, #0f172a 0%, #111827 50%, #020617 100%);
    }

    .task-card {
      touch-action: none;
      user-select: none;
    }

    .drop-active {
      outline: 2px solid rgba(255,255,255,.75);
      outline-offset: 4px;
      transform: translateY(-2px);
    }
  </style>
</head>
<body>
  <div id="root"></div>

  <script type="text/babel">
    const { useEffect, useMemo, useState } = React;

    const API = './api.php';

    const columns = [
      {
        key: 'todo',
        title: '未着手',
        label: 'Planning',
        accent: 'from-slate-400 to-slate-200',
        ring: 'ring-slate-300/30',
      },
      {
        key: 'doing',
        title: '進行中',
        label: 'Working',
        accent: 'from-sky-400 to-cyan-200',
        ring: 'ring-sky-300/30',
      },
      {
        key: 'done',
        title: '完了',
        label: 'Done',
        accent: 'from-emerald-400 to-lime-200',
        ring: 'ring-emerald-300/30',
      },
    ];

    function App() {
      const [user, setUser] = useState(null);
      const [authMode, setAuthMode] = useState('login');
      const [authForm, setAuthForm] = useState({
        name: '',
        email: '',
        password: '',
      });

      const [tasks, setTasks] = useState([]);
      const [title, setTitle] = useState('');
      const [loading, setLoading] = useState(true);
      const [saving, setSaving] = useState(false);
      const [dragging, setDragging] = useState(null);
      const [hoverColumn, setHoverColumn] = useState(null);
      const [ghost, setGhost] = useState(null);
      const [message, setMessage] = useState('');

      const grouped = useMemo(() => {
        return columns.reduce((acc, col) => {
          acc[col.key] = tasks.filter(task => task.status === col.key);
          return acc;
        }, {});
      }, [tasks]);

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

      useEffect(() => {
        if (!dragging) return;

        const onMove = (e) => {
          const x = e.clientX;
          const y = e.clientY;

          setGhost(prev => prev ? { ...prev, x, y } : null);

          const el = document.elementFromPoint(x, y);
          const columnEl = el?.closest?.('[data-column]');
          setHoverColumn(columnEl ? columnEl.dataset.column : null);
        };

        const onUp = async () => {
          if (dragging && hoverColumn && dragging.status !== hoverColumn) {
            await moveTask(dragging.id, hoverColumn);
          }

          setDragging(null);
          setHoverColumn(null);
          setGhost(null);
        };

        window.addEventListener('pointermove', onMove);
        window.addEventListener('pointerup', onUp);

        return () => {
          window.removeEventListener('pointermove', onMove);
          window.removeEventListener('pointerup', onUp);
        };
      }, [dragging, hoverColumn]);

      async function request(action, options = {}) {
        const res = await fetch(`${API}?action=${action}`, {
          credentials: 'same-origin',
          ...options,
        });

        const data = await res.json();

        if (!data.ok) {
          throw new Error(data.message || 'エラーが発生しました');
        }

        return data;
      }

      async function checkMe() {
        setLoading(true);

        try {
          const data = await request('me');

          if (data.user) {
            setUser(data.user);
            await loadTasks();
          }
        } catch (e) {
          setMessage(e.message);
        } finally {
          setLoading(false);
        }
      }

      async function submitAuth(e) {
        e.preventDefault();
        setMessage('');

        const action = authMode === 'login' ? 'login' : 'register';

        try {
          const data = await request(action, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(authForm),
          });

          setUser(data.user);
          setAuthForm({ name: '', email: '', password: '' });
          await loadTasks();
        } catch (e) {
          setMessage(e.message);
        }
      }

      async function logout() {
        try {
          await request('logout', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
          });

          setUser(null);
          setTasks([]);
        } catch (e) {
          setMessage(e.message);
        }
      }

      async function loadTasks() {
        const data = await request('list');
        setTasks(data.tasks || []);
      }

      async function addTask(e) {
        e.preventDefault();

        const cleanTitle = title.trim();
        if (!cleanTitle || saving) return;

        setSaving(true);
        setMessage('');

        try {
          const data = await request('add', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
              title: cleanTitle,
              status: 'todo',
            }),
          });

          setTasks(prev => [...prev, data.task]);
          setTitle('');
        } catch (e) {
          setMessage(e.message);
        } finally {
          setSaving(false);
        }
      }

      async function moveTask(id, status) {
        const before = tasks;

        setTasks(prev => prev.map(task => {
          if (Number(task.id) === Number(id)) {
            return { ...task, status };
          }
          return task;
        }));

        try {
          await request('move', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
              id,
              status,
            }),
          });
        } catch (e) {
          setTasks(before);
          setMessage(e.message);
        }
      }

      async function deleteTask(id) {
        const before = tasks;
        setTasks(prev => prev.filter(task => Number(task.id) !== Number(id)));

        try {
          await request('delete', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ id }),
          });
        } catch (e) {
          setTasks(before);
          setMessage(e.message);
        }
      }

      function startDrag(e, task) {
        if (e.button !== undefined && e.button !== 0) return;

        const rect = e.currentTarget.getBoundingClientRect();

        setDragging(task);
        setGhost({
          title: task.title,
          x: e.clientX,
          y: e.clientY,
          width: rect.width,
        });

        e.currentTarget.setPointerCapture?.(e.pointerId);
      }

      if (loading) {
        return (
          <div className="flex min-h-screen items-center justify-center text-white">
            <div className="rounded-3xl border border-white/10 bg-white/10 px-8 py-6 backdrop-blur">
              読み込み中...
            </div>
          </div>
        );
      }

      if (!user) {
        return (
          <div className="flex min-h-screen items-center justify-center px-4 text-white">
            <div className="w-full max-w-md rounded-3xl border border-white/10 bg-white/10 p-6 shadow-2xl shadow-black/30 backdrop-blur-xl">
              <p className="mb-3 inline-flex rounded-full border border-white/10 bg-white/10 px-4 py-1 text-sm text-white/70">
                Task Board
              </p>

              <h1 className="text-3xl font-bold">
                {authMode === 'login' ? 'ログイン' : '新規登録'}
              </h1>

              <p className="mt-2 text-sm text-white/60">
                ログインユーザーごとにタスクを保存します。
              </p>

              {message && (
                <div className="mt-5 rounded-2xl border border-red-300/20 bg-red-500/15 px-4 py-3 text-sm text-red-100">
                  {message}
                </div>
              )}

              <form onSubmit={submitAuth} className="mt-6 space-y-4">
                {authMode === 'register' && (
                  <input
                    value={authForm.name}
                    onChange={(e) => setAuthForm({ ...authForm, name: e.target.value })}
                    placeholder="名前"
                    className="w-full rounded-2xl border border-white/10 bg-white/10 px-4 py-3 text-white placeholder:text-white/40 outline-none transition focus:border-white/40"
                  />
                )}

                <input
                  type="email"
                  value={authForm.email}
                  onChange={(e) => setAuthForm({ ...authForm, email: e.target.value })}
                  placeholder="メールアドレス"
                  className="w-full rounded-2xl border border-white/10 bg-white/10 px-4 py-3 text-white placeholder:text-white/40 outline-none transition focus:border-white/40"
                />

                <input
                  type="password"
                  value={authForm.password}
                  onChange={(e) => setAuthForm({ ...authForm, password: e.target.value })}
                  placeholder="パスワード"
                  className="w-full rounded-2xl border border-white/10 bg-white/10 px-4 py-3 text-white placeholder:text-white/40 outline-none transition focus:border-white/40"
                />

                <button
                  type="submit"
                  className="w-full rounded-2xl bg-white px-5 py-3 font-bold text-slate-950 shadow-lg shadow-black/20 transition hover:-translate-y-0.5 hover:bg-cyan-100 active:translate-y-0"
                >
                  {authMode === 'login' ? 'ログイン' : '登録して開始'}
                </button>
              </form>

              <button
                type="button"
                onClick={() => {
                  setMessage('');
                  setAuthMode(authMode === 'login' ? 'register' : 'login');
                }}
                className="mt-5 w-full rounded-2xl border border-white/10 px-4 py-3 text-sm text-white/70 transition hover:bg-white/10 hover:text-white"
              >
                {authMode === 'login'
                  ? 'アカウントを作成する'
                  : 'ログイン画面に戻る'}
              </button>
            </div>
          </div>
        );
      }

      return (
        <div className="min-h-screen text-white">
          <main className="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8">
            <header className="mb-8 flex flex-col gap-5 md:flex-row md:items-end md:justify-between">
              <div>
                <p className="mb-3 inline-flex rounded-full border border-white/10 bg-white/10 px-4 py-1 text-sm text-white/70 backdrop-blur">
                  {user.name} さんのボード
                </p>

                <h1 className="text-4xl font-bold tracking-tight sm:text-5xl">
                  Task Board
                </h1>

                <p className="mt-3 text-sm text-white/60">
                  ログイン中：{user.email}
                </p>
              </div>

              <div className="flex w-full flex-col gap-3 md:w-[520px]">
                <form onSubmit={addTask} className="flex gap-2">
                  <input
                    value={title}
                    onChange={(e) => setTitle(e.target.value)}
                    placeholder="新しいタスクを入力"
                    className="min-w-0 flex-1 rounded-2xl border border-white/10 bg-white/10 px-4 py-3 text-white placeholder:text-white/40 outline-none backdrop-blur transition focus:border-white/40 focus:bg-white/15"
                  />
                  <button
                    type="submit"
                    disabled={saving}
                    className="rounded-2xl bg-white px-5 py-3 font-bold text-slate-950 shadow-lg shadow-black/20 transition hover:-translate-y-0.5 hover:bg-cyan-100 active:translate-y-0 disabled:opacity-60"
                  >
                    追加
                  </button>
                </form>

                <button
                  type="button"
                  onClick={logout}
                  className="self-end rounded-2xl border border-white/10 px-4 py-2 text-sm text-white/70 transition hover:bg-white/10 hover:text-white"
                >
                  ログアウト
                </button>
              </div>
            </header>

            {message && (
              <div className="mb-5 rounded-2xl border border-red-300/20 bg-red-500/15 px-4 py-3 text-sm text-red-100">
                {message}
              </div>
            )}

            <section className="grid gap-5 lg:grid-cols-3">
              {columns.map((col) => (
                <Column
                  key={col.key}
                  column={col}
                  tasks={grouped[col.key]}
                  active={hoverColumn === col.key}
                  dragging={dragging}
                  onDragStart={startDrag}
                  onDelete={deleteTask}
                />
              ))}
            </section>
          </main>

          {ghost && (
            <div
              className="pointer-events-none fixed z-50 rounded-2xl border border-white/20 bg-white/95 px-4 py-3 font-semibold text-slate-900 shadow-2xl"
              style={{
                left: ghost.x + 12,
                top: ghost.y + 12,
                width: ghost.width,
              }}
            >
              {ghost.title}
            </div>
          )}
        </div>
      );
    }

    function Column({ column, tasks, active, dragging, onDragStart, onDelete }) {
      return (
        <div
          data-column={column.key}
          className={[
            'min-h-[520px] rounded-3xl border border-white/10 bg-white/[.08] p-4 shadow-2xl shadow-black/20 backdrop-blur-xl transition duration-200 ring-1',
            column.ring,
            active ? 'drop-active bg-white/[.14]' : '',
          ].join(' ')}
        >
          <div className="mb-4 flex items-center justify-between">
            <div>
              <div className={`mb-2 h-1.5 w-16 rounded-full bg-gradient-to-r ${column.accent}`}></div>
              <h2 className="text-xl font-bold">{column.title}</h2>
              <p className="text-xs uppercase tracking-[.25em] text-white/40">
                {column.label}
              </p>
            </div>

            <span className="rounded-full border border-white/10 bg-white/10 px-3 py-1 text-sm text-white/70">
              {tasks.length}
            </span>
          </div>

          <div className="space-y-3">
            {tasks.length === 0 && (
              <div className="rounded-2xl border border-dashed border-white/15 p-6 text-center text-sm text-white/40">
                ここにドロップ
              </div>
            )}

            {tasks.map((task) => (
              <TaskCard
                key={task.id}
                task={task}
                dragging={dragging?.id === task.id}
                onDragStart={onDragStart}
                onDelete={onDelete}
              />
            ))}
          </div>
        </div>
      );
    }

    function TaskCard({ task, dragging, onDragStart, onDelete }) {
      return (
        <article
          onPointerDown={(e) => onDragStart(e, task)}
          className={[
            'task-card group cursor-grab rounded-2xl border border-white/10 bg-white/90 p-4 text-slate-900 shadow-lg shadow-black/10 transition duration-200 hover:-translate-y-1 hover:shadow-2xl active:cursor-grabbing',
            dragging ? 'opacity-30 scale-[.98]' : '',
          ].join(' ')}
        >
          <div className="flex items-start justify-between gap-3">
            <p className="font-semibold leading-relaxed">
              {task.title}
            </p>

            <button
              type="button"
              onPointerDown={(e) => e.stopPropagation()}
              onClick={() => onDelete(task.id)}
              className="rounded-full px-2 py-1 text-slate-400 opacity-0 transition hover:bg-slate-100 hover:text-red-500 group-hover:opacity-100"
              title="削除"
            >
              ×
            </button>
          </div>

          <div className="mt-4 flex items-center justify-between text-xs text-slate-400">
            <span>Drag to move</span>
            <span className="rounded-full bg-slate-100 px-2 py-1">#{task.id}</span>
          </div>
        </article>
      );
    }

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