// Primitive components for the ezscreenshots ASO prototype.
// All components are attached to window at the bottom so other Babel
// scripts in the page can use them.

const { useState, useEffect, useRef, useMemo, useCallback } = React;

/* ── Inline icons (no font / no SVG library) ─────────────────── */
const Icon = ({ name, size = 16, strokeWidth = 2, ...rest }) => {
  const sw = strokeWidth;
  const common = {
    width: size, height: size, viewBox: "0 0 24 24",
    fill: "none", stroke: "currentColor",
    strokeWidth: sw, strokeLinecap: "round", strokeLinejoin: "round",
    ...rest,
  };
  switch (name) {
    case "search":
      return (<svg {...common}><circle cx="11" cy="11" r="7" /><path d="m20 20-3.5-3.5" /></svg>);
    case "x":
      return (<svg {...common}><path d="M18 6 6 18" /><path d="m6 6 12 12" /></svg>);
    case "star":
      return (<svg {...common}><path d="M11.5 2.5 14 8.2l6.2.6-4.7 4.2 1.4 6L11.5 16l-5.4 3 1.4-6L2.8 8.8 9 8.2z" /></svg>);
    case "star-fill":
      return (<svg {...common} fill="currentColor" stroke="none"><path d="M11.5 2.5 14 8.2l6.2.6-4.7 4.2 1.4 6L11.5 16l-5.4 3 1.4-6L2.8 8.8 9 8.2z" /></svg>);
    case "pin":
      return (<svg {...common}><path d="M12 17v5" /><path d="M9 10.76V6a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v4.76a2 2 0 0 0 .6 1.42l2.7 2.66a1 1 0 0 1-.71 1.7H6.41a1 1 0 0 1-.71-1.7l2.7-2.66a2 2 0 0 0 .6-1.42z" /></svg>);
    case "pin-fill":
      return (<svg {...common} fill="currentColor" stroke="none"><path d="M12 17v5" stroke="#fff" strokeWidth="2" /><path d="M9 10.76V6a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v4.76a2 2 0 0 0 .6 1.42l2.7 2.66a1 1 0 0 1-.71 1.7H6.41a1 1 0 0 1-.71-1.7l2.7-2.66a2 2 0 0 0 .6-1.42z" /></svg>);
    case "arrow-right":
      return (<svg {...common}><path d="M5 12h14" /><path d="m12 5 7 7-7 7" /></svg>);
    case "external":
      return (<svg {...common}><path d="M15 3h6v6" /><path d="M10 14 21 3" /><path d="M21 14v5a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5" /></svg>);
    case "sparkle":
      return (<svg {...common}><path d="m12 3-1.5 4.5L6 9l4.5 1.5L12 15l1.5-4.5L18 9l-4.5-1.5z" /></svg>);
    case "chevron-down":
      return (<svg {...common}><path d="m6 9 6 6 6-6" /></svg>);
    case "check":
      return (<svg {...common}><path d="M20 6 9 17l-5-5" /></svg>);
    case "copy":
      return (<svg {...common}><rect x="9" y="9" width="13" height="13" rx="2" ry="2" /><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" /></svg>);
    case "expand":
      return (<svg {...common}><path d="M15 3h6v6" /><path d="M9 21H3v-6" /><path d="m21 3-7 7" /><path d="m3 21 7-7" /></svg>);
    default: return null;
  }
};

/* ── Number formatters ───────────────────────────────────────── */
const fmt = {
  num(n) {
    if (n == null) return "—";
    if (n >= 1_000_000) return (n / 1_000_000).toFixed(n >= 10_000_000 ? 0 : 1).replace(/\.0$/, "") + "M";
    if (n >= 1_000) return (n / 1_000).toFixed(n >= 10_000 ? 0 : 1).replace(/\.0$/, "") + "K";
    return String(n);
  },
  bytes(b) {
    const n = Number(b);
    if (!n || isNaN(n)) return "—";
    if (n >= 1024 ** 3) return (n / 1024 ** 3).toFixed(1) + " GB";
    if (n >= 1024 ** 2) return (n / 1024 ** 2).toFixed(0) + " MB";
    return Math.round(n / 1024) + " KB";
  },
  priorityLabel(p) {
    if (p >= 7000) return "high";
    if (p >= 4000) return "med";
    if (p >= 1500) return "low";
    return "rare";
  },
  daysAgo(iso) {
    if (!iso) return "—";
    const d = new Date(iso);
    const days = Math.floor((Date.now() - d.getTime()) / (1000 * 60 * 60 * 24));
    if (days < 1) return "today";
    if (days < 30) return `${days}d ago`;
    if (days < 365) return `${Math.floor(days / 30)}mo ago`;
    return `${Math.floor(days / 365)}y ago`;
  },
};

/* ── Highlight matches in autocomplete text ──────────────────── */
const Highlight = ({ text, q }) => {
  if (!q) return <span>{text}</span>;
  const i = text.toLowerCase().indexOf(q.toLowerCase());
  if (i < 0) return <span>{text}</span>;
  return (
    <span>
      {text.slice(0, i)}
      <mark>{text.slice(i, i + q.length)}</mark>
      {text.slice(i + q.length)}
    </span>
  );
};

/* ── Search input + autocomplete ─────────────────────────────── */
function SearchBar({ value, onChange, onSubmit, autoFocus, placeholder = "Search a keyword or app name…" }) {
  const [focused, setFocused] = useState(false);
  const [activeIdx, setActiveIdx] = useState(-1);
  const inputRef = useRef(null);
  const blurTimer = useRef(null);

  useEffect(() => {
    if (autoFocus && inputRef.current) inputRef.current.focus();
  }, [autoFocus]);

  // Live suggestions from API (debounced)
  const [suggestions, setSuggestions] = useState([]);
  const debounceRef = useRef(null);
  useEffect(() => {
    const q = value.trim();
    if (!q) { setSuggestions([]); return; }
    clearTimeout(debounceRef.current);
    debounceRef.current = setTimeout(() => {
      window.asoAPI.suggest(q).then(setSuggestions);
    }, 150);
    return () => clearTimeout(debounceRef.current);
  }, [value]);

  useEffect(() => { setActiveIdx(-1); }, [value]);

  const showDropdown = focused && value.trim() && suggestions.length > 0;

  const commit = (term) => {
    onChange(term);
    onSubmit(term);
    if (inputRef.current) inputRef.current.blur();
    setFocused(false);
  };

  const onKey = (e) => {
    if (!showDropdown) {
      if (e.key === "Enter" && value.trim()) onSubmit(value.trim());
      return;
    }
    if (e.key === "ArrowDown") { e.preventDefault(); setActiveIdx(i => Math.min(i + 1, suggestions.length - 1)); }
    else if (e.key === "ArrowUp") { e.preventDefault(); setActiveIdx(i => Math.max(i - 1, -1)); }
    else if (e.key === "Enter") {
      e.preventDefault();
      commit(activeIdx >= 0 ? suggestions[activeIdx].term : value.trim());
    } else if (e.key === "Escape") {
      setFocused(false);
      if (inputRef.current) inputRef.current.blur();
    }
  };

  return (
    <div className="search-wrap">
      <div className={"search-input-row" + (focused ? " focused" : "")}>
        <Icon name="search" size={18} className="search-icon" />
        <input
          ref={inputRef}
          type="text"
          value={value}
          onChange={e => onChange(e.target.value)}
          onFocus={() => { clearTimeout(blurTimer.current); setFocused(true); }}
          onBlur={() => { blurTimer.current = setTimeout(() => setFocused(false), 120); }}
          onKeyDown={onKey}
          placeholder={placeholder}
          spellCheck="false"
          autoComplete="off"
        />
        {value && (
          <button className="clear-btn" onMouseDown={e => e.preventDefault()} onClick={() => { onChange(""); inputRef.current && inputRef.current.focus(); }} title="Clear">
            <Icon name="x" size={16} />
          </button>
        )}
        <span className="kbd-hint">↵</span>
        <button className="go-btn" onClick={() => value.trim() && onSubmit(value.trim())} disabled={!value.trim()}>
          Research
        </button>
      </div>

      {showDropdown && (
        <div className="suggest-dropdown" onMouseDown={e => e.preventDefault()}>
          <div className="sd-cap">Suggestions · live</div>
          {suggestions.map((s, i) => (
            <div
              key={s.term}
              className={"sd-row" + (i === activeIdx ? " active" : "")}
              onMouseEnter={() => setActiveIdx(i)}
              onClick={() => commit(s.term)}
            >
              <Icon name="search" size={13} style={{ color: "var(--ui-ink-muted)" }} />
              <div className="sd-term"><Highlight text={s.term} q={value} /></div>
              <PriorityMini value={s.priority} />
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

/* ── Priority sparkbar ───────────────────────────────────────── */
function PriorityMini({ value, max = 10000 }) {
  const pct = Math.max(2, Math.min(100, (value / max) * 100));
  return (
    <div style={{ width: 60, height: 4, background: "var(--ui-bg-deep)", borderRadius: 2, overflow: "hidden" }}>
      <div style={{ width: pct + "%", height: "100%", background: "var(--accent)", borderRadius: 2 }} />
    </div>
  );
}

/* ── Star rating glyph (single, with score) ──────────────────── */
function StarBadge({ score }) {
  return (
    <span className="app-score">
      <Icon name="star-fill" size={14} />
      <span>{score.toFixed(1)}</span>
    </span>
  );
}

/* ── App icon (real image with initials fallback) ───────────── */
function AppIcon({ app, size = 56 }) {
  const [imgError, setImgError] = useState(false);
  const hasImg = app.icon && app.icon.startsWith('http') && !imgError;
  const initial = (app.title || '?').charAt(0).toUpperCase();

  if (hasImg) {
    return (
      <img
        className="app-icon"
        src={app.icon}
        alt=""
        style={{ width: size, height: size, borderRadius: size * 0.24, objectFit: 'cover' }}
        onError={() => setImgError(true)}
        loading="lazy"
      />
    );
  }
  return (
    <div className="app-icon" style={{ width: size, height: size, fontSize: size * 0.46, background: app.iconBg, borderRadius: size * 0.24 }}>
      {initial}
    </div>
  );
}

/* ── Section card wrapper ────────────────────────────────────── */
function Card({ title, meta, action, children, className = "" }) {
  return (
    <div className={"card " + className}>
      {(title || meta || action) && (
        <div className="card-head">
          <div className="ch-title">{title}</div>
          <div className="ch-meta" style={{ display: "flex", alignItems: "center", gap: 8 }}>
            {meta}
            {action}
          </div>
        </div>
      )}
      <div className="card-body">{children}</div>
    </div>
  );
}

Object.assign(window, { Icon, fmt, Highlight, SearchBar, PriorityMini, StarBadge, AppIcon, Card });
