/**
 * Component Info Panel
 *
 * Floating "datasheet" popover shown when the user hovers a card in the
 * Component Picker. It combines two data sources:
 *
 *   1. The already-loaded ComponentMetadata (name, category, pin count, live
 *      default properties, tags) — always available, no network.
 *   2. An optional hand-authored Markdown datasheet (see `componentDocs.ts`
 *      and `component-docs/`) with the richer prose, pinout, wiring tips,
 *      plus the component's brand and a purchase link. Lazy-loaded + cached.
 *
 * The panel is INTERACTIVE: the mouse can move off the card onto the panel to
 * scroll a long datasheet or click the Buy link without it closing. This is
 * driven from the modal via a grace-period hide timer — `onPanelEnter` cancels
 * the pending hide, `onPanelLeave` re-arms it. Rendered through a portal to
 * <body> so the modal's `overflow` never clips it, and flipped/clamped to stay
 * inside the viewport.
 */

import React from 'react';
import { createPortal } from 'react-dom';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import type { PropertyDescriptor } from '../types/component-metadata';
import { loadDoc, type ComponentDoc } from './componentDocs';

export interface PanelData {
  id: string; // component / board id — used to look up the Markdown doc
  name: string;
  category: string; // already display-formatted (e.g. "Sensors")
  description?: string;
  pinCount: number;
  properties: PropertyDescriptor[];
  tags: string[];
  thumbnail?: string;
  pro_only?: boolean;
}

export interface HoverTarget {
  data: PanelData;
  rect: DOMRect; // bounding box of the hovered card, in viewport coords
}

/** Delay before the panel appears — long enough to not flash on a fly-by. */
export const HOVER_DELAY = 160;

// Bulk / opaque properties that are never useful in a datasheet popover
// (base64 blobs, embedded source, framebuffers, …).
const HIDDEN_PROPS = new Set([
  'imageData',
  'wasmBase64',
  'sourceC',
  'romBytes',
  'chipJson',
  'programFile',
  'programTarget',
]);

/** Only allow real web links through to the Buy button (no javascript:, etc.). */
function safeHref(url?: string): string | null {
  if (!url) return null;
  return /^https?:\/\//i.test(url) ? url : null;
}

function formatValue(p: PropertyDescriptor): string {
  const raw = p.defaultValue;
  let def = raw === undefined || raw === null || raw === '' ? '' : String(raw);
  if (def.length > 40) def = def.slice(0, 39) + '…';

  if (p.min !== undefined || p.max !== undefined) {
    const range = `${p.min ?? '?'}–${p.max ?? '?'}`;
    return def ? `${def} (${range})` : range;
  }
  if (p.options && p.options.length) {
    const opts = p.options.join(' / ');
    // A long option list would blow out the row — fall back to the default.
    return opts.length > 44 ? def || String(p.options[0]) : opts;
  }
  return def || '—';
}

interface ComponentInfoPanelProps {
  target: HoverTarget;
  /** Called when the pointer enters the panel — cancels the pending hide. */
  onPanelEnter: () => void;
  /** Called when the pointer leaves the panel — re-arms the hide timer. */
  onPanelLeave: () => void;
}

export const ComponentInfoPanel: React.FC<ComponentInfoPanelProps> = ({
  target,
  onPanelEnter,
  onPanelLeave,
}) => {
  const ref = React.useRef<HTMLDivElement>(null);
  const [pos, setPos] = React.useState<{ left: number; top: number } | null>(null);
  const [doc, setDoc] = React.useState<ComponentDoc | null>(null);
  const { data, rect } = target;

  // Pull the authored Markdown datasheet (if any) for this id.
  React.useEffect(() => {
    let cancelled = false;
    setDoc(null);
    loadDoc(data.id).then((d) => {
      if (!cancelled) setDoc(d);
    });
    return () => {
      cancelled = true;
    };
  }, [data.id]);

  // The panel is portaled to <body>, OUTSIDE the React root container, so
  // React's synthetic onMouseEnter/onMouseLeave never fire on it (React binds
  // event delegation to the root). Attach NATIVE listeners on the node itself
  // so the "keep the panel open while the pointer is over it" bridge works.
  // Handlers are read through refs so the listeners bind once per mount.
  const enterRef = React.useRef(onPanelEnter);
  const leaveRef = React.useRef(onPanelLeave);
  enterRef.current = onPanelEnter;
  leaveRef.current = onPanelLeave;
  React.useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const onEnter = () => enterRef.current();
    const onLeave = () => leaveRef.current();
    el.addEventListener('mouseenter', onEnter);
    el.addEventListener('mouseleave', onLeave);
    return () => {
      el.removeEventListener('mouseenter', onEnter);
      el.removeEventListener('mouseleave', onLeave);
    };
  }, []);

  // Measure the rendered panel and flip/clamp it into the viewport. Runs
  // before paint so there is no visible jump from the fallback position, and
  // re-runs when the doc loads (which changes the panel's height).
  React.useLayoutEffect(() => {
    const el = ref.current;
    if (!el) return;
    const margin = 12;
    const gap = 12;
    const w = el.offsetWidth;
    const h = el.offsetHeight;
    const vw = window.innerWidth;
    const vh = window.innerHeight;

    const fitsRight = rect.right + gap + w <= vw - margin;
    const fitsLeft = rect.left - gap - w >= margin;

    let left: number;
    let top: number;
    if (fitsRight || fitsLeft) {
      // Side placement (preferred): never overlaps the card horizontally.
      left = fitsRight ? rect.right + gap : rect.left - w - gap;
      top = Math.max(margin, Math.min(rect.top, vh - h - margin));
    } else {
      // Neither side fits (narrow viewport / zoom). Dock below the card — or
      // above if there is no room — so the panel never covers its own trigger
      // and block the add-click.
      left = Math.max(margin, Math.min(rect.left, vw - w - margin));
      const below = rect.bottom + gap;
      top = below + h <= vh - margin ? below : Math.max(margin, rect.top - gap - h);
    }

    setPos({ left, top });
  }, [rect, doc]);

  const svgThumb =
    data.thumbnail && data.thumbnail.trim().startsWith('<svg') ? data.thumbnail : null;

  const visibleProps = data.properties.filter((p) => !HIDDEN_PROPS.has(p.name));
  const shownProps = visibleProps.slice(0, 8);
  const hiddenCount = visibleProps.length - shownProps.length;

  const brand = doc?.brand;
  const buyHref = safeHref(doc?.buy);

  return createPortal(
    <div
      ref={ref}
      className="component-info-panel"
      // Clicks inside the panel (Buy link, text selection) must not bubble
      // through the React portal to the overlay's onClose and shut the picker.
      onClick={(e) => e.stopPropagation()}
      style={{
        left: pos?.left ?? rect.right + 12,
        top: pos?.top ?? rect.top,
        opacity: pos ? 1 : 0,
      }}
    >
      <div className="cip-header">
        {svgThumb && (
          <div className="cip-thumb" dangerouslySetInnerHTML={{ __html: svgThumb }} />
        )}
        <div className="cip-title">
          <span className="cip-name">{data.name}</span>
          <span className="cip-badges">
            <span className="cip-cat">{data.category}</span>
            {data.pro_only && <span className="cip-pro">PRO</span>}
            {data.pinCount > 0 && <span className="cip-pins">{data.pinCount} pins</span>}
          </span>
          {brand && <span className="cip-brand">by {brand}</span>}
        </div>
      </div>

      {/* Authored datasheet supersedes the thin auto-generated description. */}
      {doc?.body ? (
        <div className="cip-doc">
          <ReactMarkdown remarkPlugins={[remarkGfm]}>{doc.body}</ReactMarkdown>
        </div>
      ) : (
        data.description && <p className="cip-desc">{data.description}</p>
      )}

      {shownProps.length > 0 && (
        <div className="cip-props">
          <div className="cip-section-title">Properties</div>
          <div className="cip-prop-list">
            {shownProps.map((p) => (
              <div className="cip-prop-row" key={p.name}>
                <span className="cip-prop-name">{p.name}</span>
                <span className="cip-prop-val">{formatValue(p)}</span>
              </div>
            ))}
          </div>
          {hiddenCount > 0 && <div className="cip-more">+{hiddenCount} more</div>}
        </div>
      )}

      {data.tags && data.tags.length > 0 && (
        <div className="cip-tags">
          {data.tags.slice(0, 6).map((t) => (
            <span className="cip-tag" key={t}>
              {t}
            </span>
          ))}
        </div>
      )}

      {buyHref && (
        <div className="cip-footer">
          <a
            className="cip-buy"
            href={buyHref}
            target="_blank"
            rel="noopener noreferrer"
          >
            <svg
              width="14"
              height="14"
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              strokeWidth="2"
              strokeLinecap="round"
              strokeLinejoin="round"
              aria-hidden="true"
            >
              <circle cx="9" cy="21" r="1" />
              <circle cx="20" cy="21" r="1" />
              <path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6" />
            </svg>
            Buy
          </a>
        </div>
      )}
    </div>,
    document.body,
  );
};
