// Author: Simon-Pierre Boucher
// Contact: contact@spboucher.ai
// Project: chat.spboucher.ai

"use client";

import { useEffect, useMemo, useRef, useState } from "react";
import { formatTokens, perMillion, providerGlyph, type ApiModel } from "./types";

interface ModelSheetProps {
  models: ApiModel[];
  selectedId: string | null;
  onSelect: (id: string) => void;
  onToggleFavorite: (id: string, favorite: boolean) => void;
  onClose: () => void;
}

type CapFilter = "reasoning" | "vision" | "tools" | "free";
type SortKey = "name" | "context" | "price" | "newest";

const PAGE = 80;

/** Subsequence fuzzy match with a crude score (lower = better). */
function fuzzyScore(query: string, target: string): number | null {
  const q = query.toLowerCase();
  const t = target.toLowerCase();
  const direct = t.indexOf(q);
  if (direct !== -1) return direct;
  let qi = 0;
  let gaps = 0;
  let last = -1;
  for (let ti = 0; ti < t.length && qi < q.length; ti++) {
    if (t[ti] === q[qi]) {
      if (last !== -1) gaps += ti - last - 1;
      last = ti;
      qi++;
    }
  }
  return qi === q.length ? 1000 + gaps : null;
}

export function ModelSheet({ models, selectedId, onSelect, onToggleFavorite, onClose }: ModelSheetProps) {
  const [query, setQuery] = useState("");
  const [caps, setCaps] = useState<Set<CapFilter>>(new Set());
  const [provider, setProvider] = useState<string | null>(null);
  const [sort, setSort] = useState<SortKey>("name");
  const [limit, setLimit] = useState(PAGE);
  const listRef = useRef<HTMLDivElement>(null);
  const inputRef = useRef<HTMLInputElement>(null);

  useEffect(() => {
    // Focus search on desktop only — on mobile the keyboard would cover the list.
    if (!window.matchMedia("(pointer: coarse)").matches) inputRef.current?.focus();
    const onKey = (e: KeyboardEvent) => e.key === "Escape" && onClose();
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [onClose]);

  const providers = useMemo(() => {
    const counts = new Map<string, number>();
    for (const m of models) {
      if (!m.available || !m.provider) continue;
      counts.set(m.provider, (counts.get(m.provider) ?? 0) + 1);
    }
    return [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 12).map(([p]) => p);
  }, [models]);

  const filtered = useMemo(() => {
    let list = models.filter((m) => m.available);
    if (provider) list = list.filter((m) => m.provider === provider);
    if (caps.has("reasoning")) list = list.filter((m) => m.capabilities.reasoning);
    if (caps.has("vision")) list = list.filter((m) => m.capabilities.vision);
    if (caps.has("tools")) list = list.filter((m) => m.capabilities.tools);
    if (caps.has("free")) list = list.filter((m) => (m.pricing?.completion ?? 0) === 0 && (m.pricing?.prompt ?? 0) === 0);

    if (query.trim()) {
      const scored: Array<{ m: ApiModel; s: number }> = [];
      for (const m of list) {
        const s = fuzzyScore(query.trim(), `${m.name} ${m.id} ${m.provider ?? ""}`);
        if (s !== null) scored.push({ m, s });
      }
      scored.sort((a, b) => a.s - b.s);
      return scored.map((x) => x.m);
    }

    const sorted = [...list];
    switch (sort) {
      case "context":
        sorted.sort((a, b) => (b.contextLength ?? 0) - (a.contextLength ?? 0));
        break;
      case "price":
        sorted.sort((a, b) => (a.pricing?.completion ?? 0) - (b.pricing?.completion ?? 0));
        break;
      case "newest":
        sorted.sort((a, b) => (b.lastUsedAt ?? 0) - (a.lastUsedAt ?? 0));
        break;
      default:
        sorted.sort((a, b) => a.name.localeCompare(b.name));
    }
    return sorted;
  }, [models, query, caps, provider, sort]);

  const favorites = useMemo(
    () => (query ? [] : filtered.filter((m) => m.favorite || m.pinned)),
    [filtered, query]
  );
  const recents = useMemo(
    () =>
      query
        ? []
        : filtered
            .filter((m) => m.lastUsedAt && !m.favorite && !m.pinned)
            .sort((a, b) => (b.lastUsedAt ?? 0) - (a.lastUsedAt ?? 0))
            .slice(0, 5),
    [filtered, query]
  );
  const rest = useMemo(() => {
    const shown = new Set([...favorites, ...recents].map((m) => m.id));
    return filtered.filter((m) => !shown.has(m.id));
  }, [filtered, favorites, recents]);

  // Incremental rendering keeps the list fast with 300+ entries.
  useEffect(() => setLimit(PAGE), [query, caps, provider, sort]);
  const onScroll = () => {
    const el = listRef.current;
    if (el && el.scrollHeight - el.scrollTop - el.clientHeight < 600 && limit < rest.length) {
      setLimit((l) => l + PAGE);
    }
  };

  const toggleCap = (c: CapFilter) => {
    const next = new Set(caps);
    if (next.has(c)) next.delete(c);
    else next.add(c);
    setCaps(next);
  };

  return (
    <>
      <div className="sheet-scrim" onClick={onClose} aria-hidden />
      <div className="sheet" role="dialog" aria-modal="true" aria-label="Choose a model">
        <div className="sheet-handle" aria-hidden />
        <div className="sheet-search">
          <input
            ref={inputRef}
            placeholder={`Search ${models.filter((m) => m.available).length} models…`}
            value={query}
            onChange={(e) => setQuery(e.target.value)}
            aria-label="Search models"
          />
          <button className="icon-btn" onClick={onClose} aria-label="Close">
            <svg width="18" height="18" viewBox="0 0 18 18" fill="none" stroke="currentColor" strokeWidth="1.6">
              <path d="m4.5 4.5 9 9m0-9-9 9" strokeLinecap="round" />
            </svg>
          </button>
        </div>

        <div className="filter-row">
          {(["reasoning", "vision", "tools", "free"] as CapFilter[]).map((c) => (
            <button key={c} className={`filter-chip${caps.has(c) ? " on" : ""}`} onClick={() => toggleCap(c)}>
              {c}
            </button>
          ))}
          <span style={{ width: 1, background: "var(--border)", flex: "none", margin: "4px 2px" }} />
          {providers.map((p) => (
            <button
              key={p}
              className={`filter-chip${provider === p ? " on" : ""}`}
              onClick={() => setProvider(provider === p ? null : p)}
            >
              {p}
            </button>
          ))}
        </div>

        <div className="filter-row" style={{ paddingTop: 0 }}>
          {(
            [
              ["name", "A–Z"],
              ["context", "context ↓"],
              ["price", "price ↑"],
              ["newest", "recent"],
            ] as [SortKey, string][]
          ).map(([k, label]) => (
            <button key={k} className={`filter-chip${sort === k ? " on" : ""}`} onClick={() => setSort(k)}>
              {label}
            </button>
          ))}
        </div>

        <div className="model-list" ref={listRef} onScroll={onScroll}>
          {favorites.length > 0 && (
            <>
              <div className="model-section-label">Favorites</div>
              {favorites.map((m) => (
                <ModelRow key={m.id} m={m} selected={m.id === selectedId} onSelect={onSelect} onToggleFavorite={onToggleFavorite} />
              ))}
            </>
          )}
          {recents.length > 0 && (
            <>
              <div className="model-section-label">Recent</div>
              {recents.map((m) => (
                <ModelRow key={m.id} m={m} selected={m.id === selectedId} onSelect={onSelect} onToggleFavorite={onToggleFavorite} />
              ))}
            </>
          )}
          <div className="model-section-label">{query ? "Results" : "All models"}</div>
          {rest.slice(0, limit).map((m) => (
            <ModelRow key={m.id} m={m} selected={m.id === selectedId} onSelect={onSelect} onToggleFavorite={onToggleFavorite} />
          ))}
          {rest.length === 0 && favorites.length === 0 && recents.length === 0 && (
            <p style={{ padding: 16, color: "var(--text-dim)", fontSize: 14 }}>
              No models match. Clear a filter or try another search.
            </p>
          )}
        </div>
      </div>
    </>
  );
}

function ModelRow({
  m,
  selected,
  onSelect,
  onToggleFavorite,
}: {
  m: ApiModel;
  selected: boolean;
  onSelect: (id: string) => void;
  onToggleFavorite: (id: string, favorite: boolean) => void;
}) {
  return (
    <div
      className={`model-row${selected ? " selected" : ""}`}
      role="button"
      tabIndex={0}
      onClick={() => onSelect(m.id)}
      onKeyDown={(e) => e.key === "Enter" && onSelect(m.id)}
    >
      <span className="prov-glyph">{providerGlyph(m.provider)}</span>
      <span className="m-main">
        <div className="m-name">{m.name}</div>
        <div className="m-sub">
          <span>{m.id}</span>
          <span>{m.contextLength ? formatTokens(m.contextLength) : "—"}</span>
          <span style={{ color: "var(--amber-500)" }}>{perMillion(m.pricing?.completion)}</span>
        </div>
      </span>
      <span className="cap-badges" aria-hidden>
        {m.capabilities.reasoning && <span className="cap-badge">R</span>}
        {m.capabilities.vision && <span className="cap-badge">V</span>}
        {m.capabilities.tools && <span className="cap-badge">T</span>}
      </span>
      <button
        className={`fav-btn${m.favorite ? " on" : ""}`}
        aria-label={m.favorite ? "Remove from favorites" : "Add to favorites"}
        onClick={(e) => {
          e.stopPropagation();
          onToggleFavorite(m.id, !m.favorite);
        }}
      >
        <svg width="16" height="16" viewBox="0 0 16 16" fill={m.favorite ? "currentColor" : "none"} stroke="currentColor" strokeWidth="1.3">
          <path d="M8 1.8l1.9 3.9 4.3.6-3.1 3 .7 4.3L8 11.6l-3.8 2 .7-4.3-3.1-3 4.3-.6z" strokeLinejoin="round" />
        </svg>
      </button>
    </div>
  );
}
