import { useEffect, useLayoutEffect, useRef, useState } from "react";
import { Check, Plus } from "lucide-react";
import { useTranslation } from "react-i18next";
import { type Tag } from "../lib/api";
import { useAnchoredPopover, PopoverSurface, PopoverItem } from "./AnchoredPopover";
import { useScrollFade } from "../lib/useScrollFade";

// The channel tag surface, shared by the channel manager (table + cards) and the channel detail
// page. Built on the R8 popover platform. Two design goals it fixes over the old inline TagsCell:
//   1. Stable edit anchor — the "+" edit trigger sits in a FIXED slot at the end, so toggling tags
//      (which grows the chip row) never moves it, and the editor popover doesn't drift.
//   2. Fixed footprint — the chip row lives in a width-bounded, single-line, overflow-hidden box and
//      collapses the excess into a "+N" pill (hover reveals the full set). The cell therefore never
//      wraps to a 2nd line, so it can't widen its table column and reflow every other row.
// The "+" is ALWAYS present (even with zero tags), and its menu can CREATE a tag inline — tagging is
// no longer gated on the top-of-page "Manage tags" dialog.

interface TagChipProps {
  tag: Tag;
  onClick?: () => void;
  title?: string;
}

function TagChip({ tag, onClick, title }: TagChipProps) {
  const cls =
    "shrink-0 text-[10px] px-1.5 py-0.5 rounded-full bg-accent text-accent-fg border border-accent max-w-[9rem] truncate";
  return onClick ? (
    <button
      type="button"
      onClick={onClick}
      title={title}
      className={`${cls} hover:opacity-80 transition`}
    >
      {tag.name}
    </button>
  ) : (
    <span className={cls}>{tag.name}</span>
  );
}

/** The editor body: toggle the user's existing tags on/off for this channel, plus an inline
 *  "create + attach" input. Rendered inside an anchored `menu` popover. (Exported for the channel
 *  detail page in Part 2; internal for now.) */
function TagEditorMenu({
  userTags,
  attachedIds,
  onToggle,
  onCreate,
}: {
  userTags: Tag[];
  attachedIds: number[];
  onToggle: (tagId: number) => void;
  onCreate: (name: string) => void;
}) {
  const { t } = useTranslation();
  const [draft, setDraft] = useState("");
  const fade = useScrollFade();
  const attached = new Set(attachedIds);
  const submit = () => {
    const name = draft.trim();
    if (!name) return;
    onCreate(name);
    setDraft("");
  };
  return (
    <div className="flex flex-col">
      <div ref={fade.ref} style={fade.style} className="max-h-[240px] overflow-y-auto no-scrollbar">
        {userTags.map((tg) => {
          const on = attached.has(tg.id);
          return (
            <PopoverItem
              key={tg.id}
              // Several tags can be on at once — a checkbox set, not one exclusive choice.
              role="menuitemcheckbox"
              checked={on}
              onSelect={() => onToggle(tg.id)}
              label={tg.name}
              className={`w-full flex items-center gap-2 text-left text-xs px-2 py-1.5 rounded-md outline-none transition hover:bg-card data-[active]:bg-[color-mix(in_srgb,var(--accent)_24%,transparent)] data-[active]:text-fg ${
                on ? "text-fg" : "text-muted"
              }`}
            >
              <span
                className={`w-3.5 h-3.5 rounded border flex items-center justify-center shrink-0 ${
                  on ? "bg-accent border-accent text-accent-fg" : "border-border"
                }`}
              >
                {on && <Check className="w-2.5 h-2.5" />}
              </span>
              <span className="truncate">{tg.name}</span>
            </PopoverItem>
          );
        })}
        {userTags.length === 0 && (
          <p className="text-[11px] text-muted px-2 py-1.5">{t("channels.tags.noneYet")}</p>
        )}
      </div>
      <div className="mt-1 pt-1.5 border-t border-border flex items-center gap-1">
        <input
          value={draft}
          onChange={(e) => setDraft(e.target.value)}
          onKeyDown={(e) => {
            // Escape bubbles to the popover's dismiss (close + refocus the trigger); every other key —
            // arrows, Home/End, typed characters — is text editing here, so keep it out of the menu's
            // list-navigation/typeahead handlers on the surrounding surface.
            if (e.key === "Escape") return;
            e.stopPropagation();
            if (e.key === "Enter") {
              e.preventDefault();
              submit();
            }
          }}
          maxLength={64}
          placeholder={t("channels.tags.createPlaceholder")}
          className="flex-1 min-w-0 bg-transparent text-xs px-2 py-1 rounded-md border border-border focus:border-accent outline-none"
        />
        <button
          type="button"
          onClick={submit}
          disabled={!draft.trim()}
          title={t("channels.tags.create")}
          aria-label={t("channels.tags.create")}
          className="shrink-0 w-6 h-6 inline-flex items-center justify-center rounded-md border border-border text-muted hover:text-accent hover:border-accent disabled:opacity-40 transition"
        >
          <Plus className="w-3.5 h-3.5" />
        </button>
      </div>
    </div>
  );
}

/** The full tag control for a channel: a width-bounded chip row with "+N" overflow (hover reveals
 *  all), followed by a stable "+" trigger that opens the shared editor. Fills its container width,
 *  so a table cell bounds it via the column width and a card bounds it via the card. */
export function ChannelTagsControl({
  userTags,
  attachedIds,
  onToggle,
  onCreate,
  onChipClick,
  bounded = true,
}: {
  userTags: Tag[];
  attachedIds: number[];
  onToggle: (tagId: number) => void;
  onCreate: (name: string) => void;
  /** Manager only: clicking a chip filters the feed by that tag. Omit on the detail page. */
  onChipClick?: (tagId: number, name: string) => void;
  /** Cap the chip row at a fixed 12rem so it can't widen a `table-layout:auto` column and reflow the
   *  table (the manager). Set false where there is free width (the channel detail header), so tags
   *  fit inline instead of needlessly collapsing into "+N". */
  bounded?: boolean;
}) {
  const { t } = useTranslation();
  const attached = userTags.filter((tg) => attachedIds.includes(tg.id));

  // Deterministic single-line fit: a hidden measurer lays out ALL chips + a sample "+N" pill at their
  // natural widths; we read each chip's right edge and compute EXACTLY how many fit, reserving the
  // pill's width whenever any overflow. One pass (no iterative re-render), so the "+N" count is always
  // right as tags are added/removed. Re-measures on width/scale change via ResizeObserver — off real
  // rem boxes, never a px guess.
  const rowRef = useRef<HTMLDivElement | null>(null);
  const measureRef = useRef<HTMLDivElement | null>(null);
  const [visible, setVisible] = useState(attached.length);
  // Key on id AND name so a RENAME (which changes a chip's width) re-runs the fit, not just add/remove.
  const tagsKey = attached.map((tg) => `${tg.id}:${tg.name}`).join("|");

  useLayoutEffect(() => {
    const row = rowRef.current;
    const measure = measureRef.current;
    if (!row || !measure) return;
    const compute = () => {
      const nodes = Array.from(measure.children) as HTMLElement[];
      const pill = nodes[nodes.length - 1];
      if (!pill) return;
      const pillW = pill.offsetWidth;
      const chips = nodes.slice(0, -1);
      // Measure the real rendered gap (gap-1 is rem — it scales with the font slider) off the box
      // between the last chip and the sample pill, rather than assuming a px constant.
      const lastChip = chips[chips.length - 1];
      const gap = lastChip
        ? Math.max(0, pill.offsetLeft - (lastChip.offsetLeft + lastChip.offsetWidth))
        : 0;
      const avail = row.clientWidth;
      // Whole-set fast path: if the LAST chip's right edge already fits, every chip fits and no "+N"
      // pill is needed. The reservation loop below reserves the pill's width for every chip except
      // the last and breaks at the first overflow, so on its own it never reaches the only "does the
      // whole set fit?" test — it would collapse chips that would have fitted (e.g. a short trailing
      // chip narrower than the pill), leaving "+N" beside empty space.
      const lastRight = lastChip ? lastChip.offsetLeft + lastChip.offsetWidth : 0;
      if (lastRight <= avail) {
        setVisible(chips.length);
        return;
      }
      let count = chips.length;
      for (let i = 0; i < chips.length; i++) {
        const chip = chips[i];
        if (!chip) break;
        const right = chip.offsetLeft + chip.offsetWidth; // right edge within the measurer
        const moreAfter = i < chips.length - 1;
        if (right > avail - (moreAfter ? pillW + gap : 0)) {
          count = i;
          break;
        }
      }
      setVisible(count);
    };
    compute();
    if (typeof ResizeObserver === "undefined") return;
    const ro = new ResizeObserver(compute);
    ro.observe(row);
    return () => ro.disconnect();
  }, [tagsKey]);

  const hidden = attached.length - visible;

  const [editorOpen, setEditorOpen] = useState(false);
  const editPop = useAnchoredPopover({
    open: editorOpen,
    onOpenChange: setEditorOpen,
    mode: "menu",
    placement: "bottom-start",
    // The menu contains a free-text "create tag" input — keep both typed chars and pointer-hover out
    // of the roving list so neither steals the field's caret.
    hostsInput: true,
  });

  const [overflowOpen, setOverflowOpen] = useState(false);
  const overflowPop = useAnchoredPopover({
    open: overflowOpen,
    onOpenChange: setOverflowOpen,
    mode: "hover",
    placement: "bottom",
  });
  // If tags change so nothing is collapsed anymore, the "+N" trigger unmounts — force the reveal-all
  // popover's state closed so it can't linger open against a gone reference.
  useEffect(() => {
    if (hidden === 0 && overflowOpen) setOverflowOpen(false);
  }, [hidden, overflowOpen]);

  const pillClass =
    "shrink-0 text-[10px] px-1.5 py-0.5 rounded-full bg-card text-muted border border-border";

  return (
    // When `bounded`, a FIXED width keeps the cell's footprint constant under `table-layout:auto`: a
    // content-driven cell re-solves EVERY column's width as chips are added, visibly shifting the whole
    // table. The fixed width keeps the table stable and centered; the chip row fits what it can and
    // collapses the rest into "+N". (max-w-full lets a narrower card shrink it.) Unbounded, the row
    // takes its container's width, so a wide header shows the tags inline instead of collapsing them.
    <div
      className={`relative flex items-center gap-1 min-w-0 ${bounded ? "w-[12rem] max-w-full" : "w-full"}`}
    >
      {/* Hidden measurer: all chips + a sample pill, laid out unbounded to read their true widths. */}
      <div
        ref={measureRef}
        aria-hidden
        className="pointer-events-none invisible absolute left-0 top-0 flex items-center gap-1 whitespace-nowrap"
      >
        {attached.map((tg) => (
          <TagChip key={tg.id} tag={tg} />
        ))}
        <span className={pillClass}>+{attached.length}</span>
      </div>

      <div ref={rowRef} className="flex items-center gap-1 min-w-0 flex-1 overflow-hidden">
        {attached.slice(0, visible).map((tg) => (
          <TagChip
            key={tg.id}
            tag={tg}
            onClick={onChipClick ? () => onChipClick(tg.id, tg.name) : undefined}
            title={onChipClick ? t("channels.row.filterFeedByTag", { name: tg.name }) : undefined}
          />
        ))}
        {hidden > 0 && (
          <button
            ref={overflowPop.refs.setReference}
            type="button"
            className={`${pillClass} hover:text-fg transition`}
            {...overflowPop.getReferenceProps()}
          >
            +{hidden}
          </button>
        )}
      </div>

      <button
        ref={editPop.refs.setReference}
        type="button"
        title={t("channels.row.editTags")}
        aria-label={t("channels.row.editTags")}
        className="shrink-0 w-5 h-5 inline-flex items-center justify-center rounded-full border border-dashed border-border text-muted hover:text-accent hover:border-accent transition"
        {...editPop.getReferenceProps()}
      >
        <Plus className="w-3 h-3" />
      </button>

      {/* Hover: reveal every attached tag when some are collapsed into "+N". */}
      <PopoverSurface
        popover={overflowPop}
        className="glass-menu max-w-[18rem] rounded-xl p-1.5 flex flex-wrap items-center gap-1 animate-[popIn_0.16s_ease]"
      >
        {attached.map((tg) => (
          <TagChip
            key={tg.id}
            tag={tg}
            onClick={onChipClick ? () => onChipClick(tg.id, tg.name) : undefined}
            title={onChipClick ? t("channels.row.filterFeedByTag", { name: tg.name }) : undefined}
          />
        ))}
      </PopoverSurface>

      {/* Click: the shared editor (toggle existing + create inline). */}
      <PopoverSurface
        popover={editPop}
        className="glass-menu w-56 rounded-xl p-1.5 animate-[popIn_0.16s_ease]"
      >
        <TagEditorMenu
          userTags={userTags}
          attachedIds={attachedIds}
          onToggle={onToggle}
          onCreate={onCreate}
        />
      </PopoverSurface>
    </div>
  );
}
