// React NodeView for the chip TipTap node.
//
// M5: chips look up their displayed label from the SysMLModel via refId.
// Click a chip → opens an inline rename popover that emits an update-block
// op via useApply(). Renaming here updates every other chip with the same
// refId AND the diagram block label.

"use client";

import { NodeViewWrapper } from "@tiptap/react";
import type { NodeViewProps } from "@tiptap/react";
import { useEffect, useRef, useState } from "react";
import type { ChipKind } from "../../lib/fixtures/aristotle";
import type { MarkupStyle } from "./Chip";
import { useChipFocus } from "./FocusContext";
import { useModelStore } from "../../lib/sync/ModelStore";
import { useAnalysis } from "../../lib/workspace/analysisStore";
import { useOpenPanes } from "../../lib/workspace/openPanesStore";
import { updateBlock, updateRequirement, updateAssociation } from "../../lib/sync/ops";

const KIND_LABEL: Record<ChipKind, string> = {
  block: "block",
  property: "property",
  association: "assoc",
  requirement: "req",
};

function kindGlyph(kind: ChipKind): string {
  switch (kind) {
    case "block": return "▢";
    case "property": return "·";
    case "association": return "→";
    case "requirement": return "§";
  }
}

export function ChipView({ node, selected, editor }: NodeViewProps) {
  const kind = (node.attrs.kind as ChipKind) ?? "block";
  const refId = (node.attrs.refId as string | null) ?? null;
  const fallbackLabel = (node.attrs.label as string) ?? "untitled";

  const markupStyle =
    ((editor.storage as unknown as Record<string, unknown>).markupStyle as MarkupStyle | undefined) ?? "color";

  const { focusBlockId, setFocusBlockId } = useChipFocus();
  const { model, apply } = useModelStore();
  const { getTerm } = useAnalysis();
  const { setStack } = useOpenPanes();

  // Resolve the live label from the model, falling back to the node's stored
  // label (e.g. for chips created via slash-menu before they're bound).
  const liveLabel = useMemo_label(model, kind, refId) ?? fallbackLabel;

  const isFocused = (refId !== null && refId === focusBlockId) || selected;
  const [isEditing, setIsEditing] = useState(false);
  const [draft, setDraft] = useState(liveLabel);
  const inputRef = useRef<HTMLInputElement | null>(null);

  useEffect(() => {
    if (isEditing) {
      setDraft(liveLabel);
      // Allow the input to mount before focusing.
      requestAnimationFrame(() => {
        inputRef.current?.focus();
        inputRef.current?.select();
      });
    }
  }, [isEditing, liveLabel]);

  function commitRename() {
    setIsEditing(false);
    const next = draft.trim();
    if (!next || next === liveLabel || !refId) return;
    if (kind === "block") {
      apply([updateBlock(refId, { label: next })]);
    } else if (kind === "requirement") {
      apply([updateRequirement(refId, { tag: next })]);
    } else if (kind === "association") {
      apply([updateAssociation(refId, { label: next })]);
    }
    // property and (potential future) constraint chips: rename in the
    // model is more involved (need parent block id resolution); skip for M5.
  }

  const cls = `chip chip-${kind} chip-style-${markupStyle}${isFocused ? " chip-focus" : ""}${isEditing ? " chip-editing" : ""}`;

  const handlers = refId
    ? {
        onMouseEnter: () => setFocusBlockId(refId),
        onMouseLeave: () => !isEditing && setFocusBlockId(null),
        onDoubleClick: (e: React.MouseEvent) => {
          e.preventDefault();
          if (refId) setIsEditing(true);
        },
        onClick: () => {
          // Single-click signals local diagram focus AND, if the chip refers
          // to a term, opens that term as a Concepts → term column chain so
          // the user lands in a coherent place.
          setFocusBlockId(refId);
          if (kind === "block" && getTerm(refId)) {
            setStack([
              { kind: "section", id: "concepts" },
              { kind: "term", id: refId },
            ]);
          }
        },
      }
    : {};

  // Inline rename input — replaces the label visual, preserves the chip frame
  if (isEditing && refId) {
    return (
      <NodeViewWrapper as="span" className={cls} contentEditable={false} draggable={false}>
        {markupStyle === "bracket" && <span className="chip-bracket">[</span>}
        {markupStyle === "bracket" && <span className="chip-kind">{KIND_LABEL[kind]}:</span>}
        {markupStyle !== "bracket" && <span className="chip-glyph">{kindGlyph(kind)}</span>}
        <input
          ref={inputRef}
          className="chip-rename"
          value={draft}
          onChange={e => setDraft(e.target.value)}
          onBlur={commitRename}
          onKeyDown={e => {
            if (e.key === "Enter") {
              e.preventDefault();
              (e.target as HTMLInputElement).blur();
            } else if (e.key === "Escape") {
              setDraft(liveLabel);
              setIsEditing(false);
            }
          }}
          size={Math.max(draft.length, 6)}
        />
        {markupStyle === "bracket" && <span className="chip-bracket">]</span>}
      </NodeViewWrapper>
    );
  }

  if (markupStyle === "bracket") {
    return (
      <NodeViewWrapper as="span" className={cls} contentEditable={false} draggable={false} {...handlers} title={refId ? "double-click to rename" : undefined}>
        <span className="chip-bracket">[</span>
        <span className="chip-kind">{KIND_LABEL[kind]}:</span>
        <span className="chip-label">{liveLabel}</span>
        <span className="chip-bracket">]</span>
      </NodeViewWrapper>
    );
  }

  if (markupStyle === "underline") {
    return (
      <NodeViewWrapper as="span" className={cls} contentEditable={false} draggable={false} {...handlers} title={refId ? "double-click to rename" : undefined}>
        <span className="chip-glyph">{kindGlyph(kind)}</span>
        <span className="chip-label">{liveLabel}</span>
      </NodeViewWrapper>
    );
  }

  return (
    <NodeViewWrapper as="span" className={cls} contentEditable={false} draggable={false} {...handlers} title={refId ? "double-click to rename" : undefined}>
      <span className="chip-glyph">{kindGlyph(kind)}</span>
      <span className="chip-label">{liveLabel}</span>
    </NodeViewWrapper>
  );
}

/**
 * Look up the live label for a chip from the model. Inline-imported and
 * collocated to keep the resolution rules near the consumer.
 *
 * For block / requirement / association / constraint chips we resolve by
 * matching `refId` against the corresponding element. Property chips use a
 * composite refId ("blockId.propertyId") — handled with a `.` split.
 */
function useMemo_label(model: ReturnType<typeof useModelStore>["model"], kind: ChipKind, refId: string | null): string | null {
  if (!refId) return null;
  if (kind === "block") {
    return model.blocks.find(b => b.id === refId)?.label ?? null;
  }
  if (kind === "requirement") {
    return model.requirements.find(r => r.id === refId || r.tag === refId)?.tag ?? null;
  }
  if (kind === "association") {
    return model.associations.find(a => a.id === refId)?.label ?? null;
  }
  if (kind === "property") {
    const dot = refId.indexOf(".");
    if (dot < 0) {
      // Bare property name from the fixture; do a flat search.
      for (const b of model.blocks) {
        const p = b.properties.find(p => p.name === refId || p.id === refId);
        if (p) return p.name;
      }
      return null;
    }
    const blockId = refId.slice(0, dot);
    const propId = refId.slice(dot + 1);
    const b = model.blocks.find(x => x.id === blockId);
    return b?.properties.find(p => p.id === propId || p.name === propId)?.name ?? null;
  }
  return null;
}
