/**
 * 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
 * - `/` alone opens completions dropdown (populated from /__COMPLETIONS.json
 *   in CLI mode, or client-side scan of the in-memory file index in dev mode)
 */

import {
  Component,
  createSignal,
  createEffect,
  onMount,
  onCleanup,
  Show,
  For,
  Switch,
  Match,
} from "solid-js";
import { sendMessage, useJournalStream } from "../stores/journalStream";
import { useJournalCompletions, ensureCompletions } from "./completions";
import { resolveRollPayload } from "./types/roll";

// ---- Helpers ----

interface CompletionItem {
  label: string;
  kind: "command" | "value" | "no-results";
  insertText: string;
}

interface ParsedInput {
  type: "chat" | "roll" | "link";
  payload: Record<string, unknown>;
  error?: string;
}

function parseInput(raw: string): ParsedInput {
  if (raw.startsWith("/roll ")) {
    const notation = raw.slice("/roll ".length).trim();
    if (!notation)
      return { type: "roll", payload: {}, error: "Dice notation required" };
    return { type: "roll", payload: { notation, label: notation } };
  }

  if (raw.startsWith("/link ")) {
    const arg = raw.slice("/link ".length).trim();
    if (!arg) return { type: "link", payload: {}, error: "Path required" };
    const hashIdx = arg.indexOf("#");
    const path = hashIdx === -1 ? arg : arg.slice(0, hashIdx);
    const section =
      hashIdx === -1 ? undefined : arg.slice(hashIdx + 1) || undefined;
    return { type: "link", payload: { path, section } };
  }

  // /roll or /link with no space — need to complete, don't send
  if (raw === "/roll" || raw === "/link") {
    return { type: "chat", payload: {}, error: "Complete the command" };
  }

  return { type: "chat", payload: { text: raw } };
}

// ---- 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;
  let completionsRef!: HTMLDivElement;

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

  // ---- Send ----

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

    // Players / observers: everything is plain chat
    if (isPlayer() || isObserver()) {
      const result = sendMessage("chat", { text: raw });
      if (!result.success) {
        setError(result.error);
      } else {
        setText("");
      }
      textareaRef?.focus();
      return;
    }

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

    setError(null);
    setSending(true);

    // GM roll: resolve the dice result locally
    if (parsed.type === "roll") {
      const p = resolveRollPayload(
        parsed.payload as { notation: string; label?: string },
      );
      const result = sendMessage("roll", p);
      if (!result.success) {
        setError(result.error);
      } else {
        setText("");
      }
      setSending(false);
      textareaRef?.focus();
      return;
    }

    const result = sendMessage(parsed.type, parsed.payload);
    if (!result.success) {
      setError(result.error);
    } else {
      setText("");
    }

    setSending(false);
    textareaRef?.focus();
  }

  // ---- Completions ----
  // ---- Scroll selected completion into view ----

  createEffect(() => {
    const idx = selectedIdx();
    if (!showCompletions() || !completionsRef) return;
    const el = completionsRef.querySelector(`[data-comp-idx="${idx}"]`);
    if (el) el.scrollIntoView({ block: "nearest" });
  });

  // ---- Completions ----

  function buildCompletions(): CompletionItem[] {
    const raw = text().trim();
    // Only GM gets completions
    if (!isGm()) return [];
    const data = comp.data;
    const commands = [
      { label: "/roll", kind: "command" as const, insertText: "/roll " },
      { label: "/link", kind: "command" as const, insertText: "/link " },
    ];

    // Show commands when user types / or starts typing a command name
    if (raw.startsWith("/") && !raw.includes(" ")) {
      const prefix = raw.toLowerCase();
      const matches = commands.filter((c) =>
        c.label.toLowerCase().startsWith(prefix),
      );
      return matches.length > 0
        ? matches
        : [{ label: "Unknown command", kind: "no-results", insertText: "" }];
    }

    // After /roll — show dice suggestions
    if (raw.startsWith("/roll ")) {
      const prefix = raw.slice("/roll ".length).toLowerCase();
      const matches = data.dice
        .filter(
          (d) =>
            d.notation.toLowerCase().includes(prefix) ||
            d.label.toLowerCase().includes(prefix),
        )
        .slice(0, 8);
      if (matches.length === 0) {
        return [{ label: "No dice found", kind: "no-results", insertText: "" }];
      }
      return matches.map((d) => ({
        label: d.notation,
        kind: "value" as const,
        insertText: "/roll " + d.notation,
      }));
    }

    // After /link — show article and heading suggestions
    if (raw.startsWith("/link ")) {
      const prefix = raw.slice("/link ".length).toLowerCase();
      const matches = data.links
        .filter(
          (l) =>
            l.path.toLowerCase().includes(prefix) ||
            l.label.toLowerCase().includes(prefix),
        )
        .slice(0, 8);
      if (matches.length === 0) {
        return [
          { label: "No links found", kind: "no-results", insertText: "" },
        ];
      }
      return matches.map((l) => {
        const insert = l.section
          ? `/link ${l.path}#${l.section}`
          : `/link ${l.path}`;
        return {
          label: l.label,
          kind: "value" as const,
          insertText: insert,
        };
      });
    }

    return [];
  }

  function currentCompletions(): CompletionItem[] {
    return buildCompletions();
  }

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

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

  // ---- Keyboard ----

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

    // When opening completions with Tab, if the text matches a command prefix
    // and there are options, accept the first. Otherwise just show.
    if (e.key === "Tab") {
      if (open) {
        e.preventDefault();
        if (comps.length > 0 && comps[selectedIdx()].kind !== "no-results") {
          acceptCompletion(comps[selectedIdx()]);
        }
        return;
      }
      // If not open and starts with /, open completions (GM only)
      const raw = text();
      if (isGm() && raw.startsWith("/")) {
        e.preventDefault();
        setShowCompletions(true);
        setSelectedIdx(0);
        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;
      }
    }

    // Send
    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);

    // Completions visibility — keep open while user types any / (GM only)
    if (isGm() && raw.startsWith("/")) {
      setShowCompletions(true);
      setSelectedIdx(0);
    } else {
      setShowCompletions(false);
    }

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

  // ---- Click outside ----

  onMount(() => {
    const handler = (e: MouseEvent) => {
      if (completionsRef && !completionsRef.contains(e.target as Node)) {
        setShowCompletions(false);
      }
    };
    document.addEventListener("mousedown", handler);
    onCleanup(() => document.removeEventListener("mousedown", handler));
  });

  // Completions placeholder — shown in the dropdown area when no matches
  function renderCompletionsDropdown() {
    const comps = currentCompletions();
    if (comps.length === 0) return null;

    return (
      <div
        ref={completionsRef}
        class="absolute bottom-full left-0 right-0 bg-white border border-gray-200 rounded-t shadow-lg mb-0.5 max-h-40 overflow-y-auto z-50"
      >
        <For each={comps}>
          {(item, idx) => (
            <div
              data-comp-idx={idx()}
              class={`flex items-center gap-2 px-3 py-1.5 cursor-pointer text-sm ${
                item.kind === "no-results"
                  ? "text-gray-400 italic cursor-default"
                  : idx() === selectedIdx()
                    ? "bg-blue-50 text-blue-700"
                    : "hover:bg-gray-100 text-gray-700"
              }`}
              onClick={() => acceptCompletion(item)}
              onMouseEnter={() =>
                item.kind !== "no-results" && setSelectedIdx(idx())
              }
            >
              <Show
                when={item.kind !== "no-results"}
                fallback={<span class="text-xs text-gray-300 shrink-0">—</span>}
              >
                <span
                  class={`text-xs px-1 rounded shrink-0 ${
                    item.kind === "command"
                      ? "bg-purple-100 text-purple-700"
                      : "bg-gray-100 text-gray-500"
                  }`}
                >
                  {item.kind === "command" ? "cmd" : "val"}
                </span>
              </Show>
              <span class="font-mono truncate flex-1">{item.label}</span>
            </div>
          )}
        </For>
      </div>
    );
  }

  return (
    <div class="border-t border-gray-200 bg-white relative">
      {/* Completions loading / error / empty teaser */}
      <Show when={showCompletions()}>
        <Switch>
          <Match when={comp.state.status === "loading"}>
            <div
              ref={completionsRef}
              class="absolute bottom-full left-0 right-0 bg-white border border-gray-200 rounded-t shadow-lg mb-0.5 z-50 px-3 py-2 text-xs text-gray-400 italic"
            >
              Loading completions…
            </div>
          </Match>
          <Match when={comp.state.status === "error"}>
            <div
              ref={completionsRef}
              class="absolute bottom-full left-0 right-0 bg-white border border-red-200 rounded-t shadow-lg mb-0.5 z-50 px-3 py-2 text-xs text-red-500"
            >
              {(comp.state as any).message || "Failed to load completions"}
            </div>
          </Match>
          <Match
            when={
              comp.state.status === "empty" || comp.state.status === "loaded"
            }
          >
            {renderCompletionsDropdown()}
          </Match>
        </Switch>
      </Show>

      {/* Textarea + actions */}
      <div class="flex flex-col">
        <Show
          when={!isObserver()}
          fallback={
            <div class="px-3 py-4 text-xs text-gray-400 italic text-center">
              You are observing. Open this panel on another device to join as GM
              or player.
            </div>
          }
        >
          <textarea
            ref={textareaRef}
            value={text()}
            onInput={handleInput}
            onKeyDown={handleKeyDown}
            placeholder={
              isGm()
                ? "Type a message, or /roll or /link..."
                : "Type a message..."
            }
            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]"
          />

          {/* Bottom row: error + buttons */}
          <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"
              >
                Send
              </button>
            </div>
          </div>
        </Show>
      </div>
    </div>
  );
};
