/**
 * JournalInput — chat-style textarea with command dispatch and autocomplete.
 *
 * - Enter sends, Shift+Enter inserts newline
 * - Plain text → "chat" type
 * - `/roll 3d6kh1` → "roll" type (result resolved client-side by GM)
 * - `/link path#section` → "link" type
 * - `/stat set key=value` → "stat" type
 * - `/` alone opens completions dropdown
 */

import { Component, createSignal, createEffect, onMount, Show } from "solid-js";
import { sendMessage, useJournalStream } from "../stores/journalStream";
import { actionPrefill, setActionPrefill } from "../stores/reveal";
import { useJournalCompletions, ensureCompletions } from "./completions";
import { resolveRollPayload } from "./types/roll";
import { resolveSparkPayload } from "./types/spark";
import { resolveStatRoll, canModifyStat } from "./stat-helpers";
import { parseInput } from "./command-parser";
import type { CompletionItem } from "./command-parser";
import { buildCompletions } from "./command-completions";
import { CompletionsDropdown } from "./CompletionsDropdown";

// ---- Component ----

export const JournalInput: Component = () => {
  const stream = useJournalStream();
  const comp = useJournalCompletions();

  const isObserver = () => stream.myRole === "observer";
  const isPlayer = () => stream.myRole === "player";
  const isGm = () => stream.myRole === "gm";

  const [text, setText] = createSignal("");
  const [error, setError] = createSignal<string | null>(null);
  const [sending, setSending] = createSignal(false);
  const [showCompletions, setShowCompletions] = createSignal(false);
  const [selectedIdx, setSelectedIdx] = createSignal(0);

  let textareaRef!: HTMLTextAreaElement;

  // Ensure completions are loading on mount
  onMount(() => {
    void ensureCompletions();
  });

  // Listen for action prefill requests from article buttons
  createEffect(() => {
    const action = actionPrefill();
    if (action) {
      setText(`${action.command} ${action.text}`);
      setActionPrefill(null);
      textareaRef?.focus();
    }
  });

  // ---- Derived ----

  const completions = () =>
    buildCompletions(text().trim(), {
      data: comp.data,
      isGm: isGm(),
    });

  // ---- Send ----

  async function handleSend() {
    const raw = text().trim();
    if (!raw) return;

    // Observers: everything is plain chat
    if (isObserver()) {
      const result = sendMessage("chat", { text: raw });
      const r = unwrap(result);
      finish(r.ok, r.err);
      return;
    }

    const parsed = parseInput(raw);
    if (parsed.error) {
      setError(parsed.error);
      return;
    }

    setError(null);

    // Players: chat + stat commands only
    if (isPlayer()) {
      if (parsed.type === "chat") {
        const result = sendMessage("chat", { text: raw });
        const r = unwrap(result);
        finish(r.ok, r.err);
        return;
      }

      if (parsed.type === "stat") {
        handleStat(parsed.payload);
        return;
      }

      setError("玩家只能发送聊天消息或使用 /stat 命令");
      return;
    }

    // GM: all commands
    setSending(true);

    if (parsed.type === "roll") {
      const p = resolveRollPayload(
        parsed.payload as { notation: string; label?: string },
      );
      const result = sendMessage("roll", p);
      const r = unwrap(result);
      finish(r.ok, r.err);
      return;
    }

    if (parsed.type === "spark") {
      try {
        const key = (parsed.payload as { key: string }).key;
        const match = comp.data.sparkTables.find((s) => s.slug === key);
        const filePath = match?.filePath ?? "";
        const p = await resolveSparkPayload({ key, filePath });
        const result = sendMessage("spark", p);
        const r = unwrap(result);
        finish(r.ok, r.err);
      } catch (e) {
        setError(e instanceof Error ? e.message : "种子表掷骰失败");
        setSending(false);
      }
      return;
    }

    if (parsed.type === "stat") {
      handleStat(parsed.payload);
      setSending(false);
      return;
    }

    const result = sendMessage(parsed.type, parsed.payload);
    const r = unwrap(result);
    finish(r.ok, r.err);
  }

  /** Handle a /stat command payload (set/del/roll) */
  function handleStat(payload: Record<string, unknown>) {
    const p = payload as { action?: string; key?: string; value?: string };

    if (p.action === "roll" && p.key) {
      const resolved = resolveStatRoll(p.key, comp.data.stats, stream.stats);
      if (resolved.error) {
        setError(resolved.error);
      } else {
        const result = sendMessage("stat", {
          action: "set",
          key: p.key,
          value: resolved.value,
        });
        const r = unwrap(result);
        finish(r.ok, r.err);
      }
      return;
    }

    if (!p.action || !p.key) {
      setError("无效的 stat 命令");
      return;
    }

    if (!canModifyStat(stream.myRole, stream.myName, p.key)) {
      setError(`无权修改属性: ${p.key}`);
      return;
    }

    const result = sendMessage("stat", payload);
    const r = unwrap(result);
    finish(r.ok, r.err);
  }

  /** Clear text + error on success, or set error on failure. */
  function finish(success: boolean, err?: string) {
    if (success) {
      setText("");
    } else if (err) {
      setError(err);
    }
    setSending(false);
    textareaRef?.focus();
  }

  /** Unwrap a sendMessage result into (success, error?) for finish(). */
  function unwrap<R>(
    r: { success: true; msg: R } | { success: false; error: string },
  ) {
    return r.success
      ? ({ ok: true, err: undefined } as const)
      : ({ ok: false, err: r.error } as const);
  }

  // ---- Completions ----

  function acceptCompletion(item: CompletionItem) {
    if (item.kind === "no-results") return;
    setText(item.insertText);
    setShowCompletions(false);
    textareaRef?.focus();
  }

  function selectCompletion(dir: "up" | "down") {
    const comps = completions();
    if (comps.length === 0) return;
    setSelectedIdx((prev) => {
      if (dir === "down") return (prev + 1) % comps.length;
      return (prev - 1 + comps.length) % comps.length;
    });
  }

  function openCompletions() {
    setShowCompletions(true);
    setSelectedIdx(0);
  }

  // ---- Keyboard ----

  function handleKeyDown(e: KeyboardEvent) {
    const comps = completions();
    const open = showCompletions();

    if (e.key === "Tab") {
      if (open) {
        e.preventDefault();
        if (comps.length > 0 && comps[selectedIdx()].kind !== "no-results") {
          acceptCompletion(comps[selectedIdx()]);
        }
        return;
      }
      const raw = text();
      if ((isGm() || raw.startsWith("/stat")) && raw.startsWith("/")) {
        e.preventDefault();
        openCompletions();
        return;
      }
      return;
    }

    if (open && comps.length > 0) {
      if (e.key === "ArrowDown") {
        e.preventDefault();
        selectCompletion("down");
        return;
      }
      if (e.key === "ArrowUp") {
        e.preventDefault();
        selectCompletion("up");
        return;
      }
      if (e.key === "Enter" && comps[selectedIdx()].kind !== "no-results") {
        e.preventDefault();
        acceptCompletion(comps[selectedIdx()]);
        return;
      }
      if (e.key === "Escape") {
        setShowCompletions(false);
        return;
      }
    }

    if (e.key === "Enter" && !e.shiftKey) {
      e.preventDefault();
      handleSend();
    }
  }

  function handleInput(e: InputEvent) {
    const input = e.currentTarget as HTMLTextAreaElement;
    const raw = input.value;
    setText(raw);

    if ((isGm() || raw.startsWith("/stat")) && raw.startsWith("/")) {
      setShowCompletions(true);
      setSelectedIdx(0);
    } else {
      setShowCompletions(false);
    }

    // Auto-resize
    textareaRef.style.height = "auto";
    textareaRef.style.height = Math.min(textareaRef.scrollHeight, 150) + "px";
  }

  // ---- Render ----

  return (
    <div class="border-t border-gray-200 bg-white relative">
      <CompletionsDropdown
        show={showCompletions()}
        items={completions()}
        selectedIdx={selectedIdx()}
        onSelect={acceptCompletion}
        onHover={(idx) => setSelectedIdx(idx)}
        onClose={() => setShowCompletions(false)}
        state={comp.state}
      />

      <div class="flex flex-col">
        <Show
          when={!isObserver()}
          fallback={
            <div class="px-3 py-4 text-xs text-gray-400 italic text-center">
              你正在观察模式。在另一台设备上打开此面板以主持人或玩家身份加入。
            </div>
          }
        >
          <textarea
            ref={textareaRef}
            id="journal-input-textarea"
            value={text()}
            onInput={handleInput}
            onKeyDown={handleKeyDown}
            placeholder={
              isGm()
                ? "输入消息，或使用 /roll、/spark、/link、/stat 命令..."
                : "输入消息，或使用 /stat 命令..."
            }
            rows={2}
            class="w-full resize-none border-0 px-3 pt-2.5 pb-1 text-sm
                   text-gray-800 placeholder-gray-400 focus:outline-none
                   bg-transparent min-h-[60px]"
          />

          <div class="flex items-center justify-between px-2 pb-2">
            <Show when={error()}>
              <p class="text-red-500 text-xs truncate max-w-[60%]">{error()}</p>
            </Show>
            <div class="flex items-center gap-1 ml-auto">
              <button
                onClick={handleSend}
                disabled={sending() || !text().trim()}
                class="bg-blue-600 text-white rounded px-3 py-1 text-xs
                     hover:bg-blue-700 disabled:opacity-40 disabled:cursor-not-allowed
                     transition-colors"
              >
                发送
              </button>
            </div>
          </div>
        </Show>
      </div>
    </div>
  );
};
