// ============================================
// Shared components for 社内システム共通マスター登録
// ============================================
const { useState, useEffect, useRef, useMemo, useCallback, createContext, useContext } = React;

// ====== Icons (heroicons-style, 16x16) ======
const Icon = ({ name, size = 16, ...rest }) => {
  const props = { width: size, height: size, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round", ...rest };
  switch (name) {
    case "search":   return <svg {...props}><circle cx="7" cy="7" r="4.5"/><path d="m10.5 10.5 3 3"/></svg>;
    case "plus":     return <svg {...props}><path d="M8 3v10M3 8h10"/></svg>;
    case "close":    return <svg {...props}><path d="M3.5 3.5l9 9M12.5 3.5l-9 9"/></svg>;
    case "check":    return <svg {...props}><path d="M3 8.5l3 3 7-7"/></svg>;
    case "more":     return <svg {...props}><circle cx="3" cy="8" r="1" fill="currentColor"/><circle cx="8" cy="8" r="1" fill="currentColor"/><circle cx="13" cy="8" r="1" fill="currentColor"/></svg>;
    case "edit":     return <svg {...props}><path d="M11 2.5l2.5 2.5L6 12.5l-3 .5.5-3z"/></svg>;
    case "trash":    return <svg {...props}><path d="M2.5 4h11M6 4V2.5h4V4M4 4l.5 9h7L12 4M6.5 6.5v4.5M9.5 6.5v4.5"/></svg>;
    case "copy":     return <svg {...props}><rect x="5.5" y="5.5" width="8" height="8" rx="1"/><path d="M3 10.5V3a.5.5 0 0 1 .5-.5H11"/></svg>;
    case "filter":   return <svg {...props}><path d="M2 3.5h12L9.5 9v3.5L6.5 14V9z"/></svg>;
    case "download": return <svg {...props}><path d="M8 2.5v8M4.5 7l3.5 3.5L11.5 7M2.5 13h11"/></svg>;
    case "upload":   return <svg {...props}><path d="M8 10.5v-8M4.5 6L8 2.5 11.5 6M2.5 13h11"/></svg>;
    case "chevron-down":  return <svg {...props}><path d="M3.5 6l4.5 4.5L12.5 6"/></svg>;
    case "chevron-right": return <svg {...props}><path d="M6 3.5L10.5 8 6 12.5"/></svg>;
    case "chevron-left":  return <svg {...props}><path d="M10 3.5L5.5 8 10 12.5"/></svg>;
    case "users":    return <svg {...props}><circle cx="6" cy="6" r="2.5"/><path d="M2 13c0-2.2 1.8-4 4-4s4 1.8 4 4"/><path d="M10 5.5a2 2 0 1 1 0 4"/><path d="M14 13c0-1.5-1-2.8-2.5-3.4"/></svg>;
    case "film":     return <svg {...props}><rect x="2" y="2.5" width="12" height="11" rx="1"/><path d="M2 5.5h12M2 10.5h12M5 2.5v11M11 2.5v11"/></svg>;
    case "building": return <svg {...props}><rect x="3" y="2" width="10" height="12" rx="0.5"/><path d="M5.5 5h1M9.5 5h1M5.5 8h1M9.5 8h1M5.5 11h1M9.5 11h1"/></svg>;
    case "briefcase": return <svg {...props}><rect x="2" y="5" width="12" height="9" rx="1"/><path d="M5.5 5V3.5a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1V5"/><path d="M2 9h12"/></svg>;
    case "tag":      return <svg {...props}><path d="M2 2h5l7 7-5 5-7-7z"/><circle cx="5" cy="5" r=".8" fill="currentColor"/></svg>;
    case "history":  return <svg {...props}><path d="M2.5 8a5.5 5.5 0 1 0 1.6-3.9"/><path d="M2.5 3v3h3"/><path d="M8 5v3.5l2 1.5"/></svg>;
    case "menu":     return <svg {...props}><path d="M2 4h12M2 8h12M2 12h12"/></svg>;
    case "grid":     return <svg {...props}><rect x="2" y="2" width="5" height="5" rx="0.5"/><rect x="9" y="2" width="5" height="5" rx="0.5"/><rect x="2" y="9" width="5" height="5" rx="0.5"/><rect x="9" y="9" width="5" height="5" rx="0.5"/></svg>;
    case "shield":   return <svg {...props}><path d="M8 1.5L2.5 3.5v4c0 3 2.5 5.5 5.5 7 3-1.5 5.5-4 5.5-7v-4z"/></svg>;
    case "logout":   return <svg {...props}><path d="M9 11.5v1.5a1 1 0 0 1-1 1H3.5a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1H8a1 1 0 0 1 1 1v1.5M6.5 8h7M11 5.5L13.5 8 11 10.5"/></svg>;
    case "sun":      return <svg {...props}><circle cx="8" cy="8" r="3"/><path d="M8 2v1.5M8 12.5V14M2 8h1.5M12.5 8H14M3.8 3.8l1 1M11.2 11.2l1 1M3.8 12.2l1-1M11.2 4.8l1-1"/></svg>;
    case "sliders":  return <svg {...props}><path d="M2 5h6M11 5h3M2 11h3M8 11h6"/><circle cx="9.5" cy="5" r="1.2" fill="white"/><circle cx="6.5" cy="11" r="1.2" fill="white"/></svg>;
    case "info":     return <svg {...props}><circle cx="8" cy="8" r="6"/><path d="M8 7.5v3.5M8 5.5v.5"/></svg>;
    case "warning":  return <svg {...props}><path d="M8 2L14 13H2z"/><path d="M8 7v3M8 11.5v.5"/></svg>;
    case "lock":     return <svg {...props}><rect x="3" y="7" width="10" height="7" rx="1"/><path d="M5 7V5a3 3 0 0 1 6 0v2"/></svg>;
    case "dot":      return <svg {...props}><circle cx="8" cy="8" r="2" fill="currentColor"/></svg>;
    default:         return <svg {...props}><circle cx="8" cy="8" r="6"/></svg>;
  }
};

// ====== Button ======
const Button = ({ children, icon, iconRight, variant, size, onClick, disabled, type = "button", title, iconOnly }) => (
  <button
    type={type}
    onClick={onClick}
    disabled={disabled}
    title={title}
    className="btn"
    data-variant={variant}
    data-size={size}
    data-icon-only={iconOnly ? "true" : undefined}
  >
    {icon && <Icon name={icon} size={size === "sm" ? 12 : 13} />}
    {children}
    {iconRight && <Icon name={iconRight} size={size === "sm" ? 12 : 13} />}
  </button>
);

const IconButton = ({ icon, onClick, title, size = 16 }) => (
  <button type="button" className="icon-btn" onClick={onClick} title={title}>
    <Icon name={icon} size={size} />
  </button>
);

// ====== Input / Select / Textarea ======
const Input = ({ value, onChange, placeholder, type = "text", autoFocus, onKeyDown, ...rest }) => (
  <input
    className="input"
    type={type}
    value={value ?? ""}
    onChange={e => onChange?.(e.target.value)}
    placeholder={placeholder}
    autoFocus={autoFocus}
    onKeyDown={onKeyDown}
    {...rest}
  />
);

const Select = ({ value, onChange, options, placeholder }) => (
  <select className="input" value={value ?? ""} onChange={e => onChange?.(e.target.value)}>
    {placeholder !== undefined && <option value="">{placeholder}</option>}
    {options.map(o => (
      <option key={o.value} value={o.value}>{o.label}</option>
    ))}
  </select>
);

const Textarea = ({ value, onChange, placeholder, rows = 3 }) => (
  <textarea
    className="input"
    rows={rows}
    value={value ?? ""}
    onChange={e => onChange?.(e.target.value)}
    placeholder={placeholder}
  />
);

const Field = ({ label, required, error, hint, children, span }) => (
  <div className="field" style={span === 2 ? { gridColumn: "span 2" } : undefined}>
    {label && (
      <label className="field-label">
        {label}
        {required && <span className="req">*</span>}
      </label>
    )}
    {children}
    {error && <div className="field-error">{error}</div>}
    {hint && !error && <div className="field-hint">{hint}</div>}
  </div>
);

const SearchInput = ({ value, onChange, placeholder = "検索...", style }) => (
  <div className="input-search" style={style}>
    <Icon name="search" size={14} className="search-icon" />
    <input
      className="input"
      value={value}
      onChange={e => onChange(e.target.value)}
      placeholder={placeholder}
    />
  </div>
);

// ====== Badge / Tag / Avatar ======
const Badge = ({ tone, children, dot }) => (
  <span className="badge" data-tone={tone}>
    {dot && <span className="status-dot" />}
    {children}
  </span>
);

const Tag = ({ children }) => <span className="tag">{children}</span>;

const Avatar = ({ id, name, size, src }) => {
  const [bg, fg] = avatarColor(id || name || "?");
  const init = name?.slice(0, 2) || (id || "?").slice(-2);
  return (
    <span className="avatar" data-size={size} style={{ background: bg, color: fg, borderColor: bg }}>
      {src ? <img src={src} alt="" style={{ width: "100%", height: "100%", objectFit: "cover" }} /> : init}
    </span>
  );
};

const AvatarName = ({ staff, sub }) => {
  if (!staff) return <span className="muted">—</span>;
  const name = `${staff.lastName} ${staff.firstName}`;
  const kana = `${staff.lastKana || ""} ${staff.firstKana || ""}`.trim();
  return (
    <span className="avatar-name">
      <Avatar id={staff.id} name={initialsFor(staff)} />
      <span className="avatar-name-text">
        <span className="name-main">{name}</span>
        <span className="name-sub">{sub ?? kana}</span>
      </span>
    </span>
  );
};

// ====== Checkbox ======
const Checkbox = ({ checked, onChange, indeterminate }) => {
  const ref = useRef(null);
  useEffect(() => { if (ref.current) ref.current.indeterminate = !!indeterminate; }, [indeterminate]);
  return (
    <input
      ref={ref}
      type="checkbox"
      className="cb"
      checked={!!checked}
      onChange={e => onChange?.(e.target.checked)}
      onClick={e => e.stopPropagation()}
    />
  );
};

// ====== Modal ======
const Modal = ({ open, onClose, title, subtitle, size, children, footer }) => {
  useEffect(() => {
    if (!open) return;
    const onKey = (e) => { if (e.key === "Escape") onClose?.(); };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [open, onClose]);
  if (!open) return null;
  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal" data-size={size} onClick={e => e.stopPropagation()}>
        <div className="modal-header">
          <div style={{ flex: 1, minWidth: 0 }}>
            <h3 className="modal-title">{title}</h3>
            {subtitle && <div className="modal-subtitle">{subtitle}</div>}
          </div>
          <IconButton icon="close" onClick={onClose} title="閉じる" />
        </div>
        <div className="modal-body">{children}</div>
        {footer && <div className="modal-footer">{footer}</div>}
      </div>
    </div>
  );
};

// ====== Confirm dialog ======
const ConfirmDialog = ({ open, title, message, onCancel, onConfirm, confirmLabel = "削除", danger = true }) => (
  <Modal
    open={open}
    onClose={onCancel}
    title={title}
    size="sm"
    footer={
      <>
        <Button onClick={onCancel}>キャンセル</Button>
        <Button variant={danger ? "danger" : "accent"} onClick={onConfirm}>{confirmLabel}</Button>
      </>
    }
  >
    <p style={{ margin: 0, fontSize: 13, lineHeight: 1.6 }}>{message}</p>
  </Modal>
);

// ====== Dropdown menu ======
const DropdownMenu = ({ trigger, items, align = "left" }) => {
  const [open, setOpen] = useState(false);
  const ref = useRef(null);
  useEffect(() => {
    if (!open) return;
    const onDown = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    window.addEventListener("mousedown", onDown);
    return () => window.removeEventListener("mousedown", onDown);
  }, [open]);
  return (
    <div ref={ref} style={{ position: "relative", display: "inline-block" }}>
      <span onClick={() => setOpen(o => !o)}>{trigger}</span>
      {open && (
        <div className="dropdown" style={{ top: "calc(100% + 4px)", [align]: 0 }}>
          {items.map((it, i) => {
            if (it.type === "divider") return <div key={i} className="dropdown-divider" />;
            if (it.type === "label")   return <div key={i} className="dropdown-label">{it.label}</div>;
            return (
              <button
                key={i}
                className="dropdown-item"
                data-danger={it.danger || undefined}
                onClick={() => { setOpen(false); it.onClick?.(); }}
              >
                {it.icon && <Icon name={it.icon} size={13} />}
                <span style={{ flex: 1 }}>{it.label}</span>
                {it.shortcut && <span className="shortcut">{it.shortcut}</span>}
              </button>
            );
          })}
        </div>
      )}
    </div>
  );
};

// ====== Data Table ======
const DataTable = ({ columns, rows, rowKey = "id", onRowClick, sort, onSortChange, selected, onSelectedChange, rowActions, emptyMessage = "データがありません" }) => {
  const allSelected = selected && rows.length > 0 && rows.every(r => selected.has(r[rowKey]));
  const someSelected = selected && rows.some(r => selected.has(r[rowKey])) && !allSelected;
  const hasSelection = !!selected;

  return (
    <div className="table-wrap">
      <div className="table-scroll">
        <table className="data-table">
          <thead>
            <tr>
              {hasSelection && (
                <th className="col-check">
                  <Checkbox
                    checked={allSelected}
                    indeterminate={someSelected}
                    onChange={(v) => {
                      const next = new Set(selected);
                      if (v) rows.forEach(r => next.add(r[rowKey]));
                      else rows.forEach(r => next.delete(r[rowKey]));
                      onSelectedChange(next);
                    }}
                  />
                </th>
              )}
              {columns.map(col => {
                const sorted = sort?.key === col.key ? sort.dir : null;
                return (
                  <th
                    key={col.key}
                    data-sortable={col.sortable ? "true" : undefined}
                    data-sorted={sorted ? "true" : undefined}
                    style={{ width: col.width, minWidth: col.minWidth }}
                    onClick={col.sortable ? () => {
                      if (!onSortChange) return;
                      const nextDir = sort?.key === col.key && sort.dir === "asc" ? "desc" : "asc";
                      onSortChange({ key: col.key, dir: nextDir });
                    } : undefined}
                  >
                    {col.label}
                    {col.sortable && (
                      <span className="sort-ind">
                        {sorted === "asc" ? "▲" : sorted === "desc" ? "▼" : "↕"}
                      </span>
                    )}
                  </th>
                );
              })}
              {rowActions && <th className="col-actions"></th>}
            </tr>
          </thead>
          <tbody>
            {rows.length === 0 && (
              <tr>
                <td colSpan={columns.length + (hasSelection ? 1 : 0) + (rowActions ? 1 : 0)}>
                  <div className="empty-state">
                    <h3>{emptyMessage}</h3>
                    <p>条件を変えて再度お試しください</p>
                  </div>
                </td>
              </tr>
            )}
            {rows.map(row => {
              const isSel = selected?.has(row[rowKey]);
              return (
                <tr
                  key={row[rowKey]}
                  data-selected={isSel ? "true" : undefined}
                  onClick={() => onRowClick?.(row)}
                  style={{ cursor: onRowClick ? "pointer" : undefined }}
                >
                  {hasSelection && (
                    <td className="col-check">
                      <Checkbox
                        checked={isSel}
                        onChange={(v) => {
                          const next = new Set(selected);
                          if (v) next.add(row[rowKey]); else next.delete(row[rowKey]);
                          onSelectedChange(next);
                        }}
                      />
                    </td>
                  )}
                  {columns.map(col => (
                    <td key={col.key} style={{ width: col.width, textAlign: col.align }}>
                      {col.render ? col.render(row) : row[col.key]}
                    </td>
                  ))}
                  {rowActions && (
                    <td className="col-actions" onClick={e => e.stopPropagation()}>
                      <span className="row-actions">{rowActions(row)}</span>
                    </td>
                  )}
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>
    </div>
  );
};

// ====== Pagination ======
const Pagination = ({ page, pageSize, total, onPageChange, onPageSizeChange }) => {
  const totalPages = Math.max(1, Math.ceil(total / pageSize));
  const start = total === 0 ? 0 : (page - 1) * pageSize + 1;
  const end = Math.min(total, page * pageSize);

  // build page button list (compact: 1 ... 4 5 [6] 7 8 ... 12)
  const pageNums = [];
  const add = (n) => pageNums.push(n);
  if (totalPages <= 7) {
    for (let i = 1; i <= totalPages; i++) add(i);
  } else {
    add(1);
    if (page > 4) add("…");
    for (let i = Math.max(2, page - 2); i <= Math.min(totalPages - 1, page + 2); i++) add(i);
    if (page < totalPages - 3) add("…");
    add(totalPages);
  }

  return (
    <div className="pagination">
      <span>{start.toLocaleString()}–{end.toLocaleString()} / {total.toLocaleString()}件</span>
      <span className="toolbar-divider" />
      <span>表示</span>
      <select
        className="input"
        style={{ height: 24, padding: "0 8px", fontSize: 11.5, width: 60 }}
        value={pageSize}
        onChange={e => onPageSizeChange(Number(e.target.value))}
      >
        {[10, 25, 50, 100].map(n => <option key={n} value={n}>{n}</option>)}
      </select>
      <span style={{ flex: 1 }} />
      <button className="page-btn" disabled={page <= 1} onClick={() => onPageChange(page - 1)}>
        <Icon name="chevron-left" size={12} />
      </button>
      <span className="pages">
        {pageNums.map((n, i) =>
          n === "…"
            ? <span key={"e"+i} className="page-btn" style={{ cursor: "default" }}>…</span>
            : <button key={n} className="page-btn" data-active={n === page ? "true" : undefined} onClick={() => onPageChange(n)}>{n}</button>
        )}
      </span>
      <button className="page-btn" disabled={page >= totalPages} onClick={() => onPageChange(page + 1)}>
        <Icon name="chevron-right" size={12} />
      </button>
    </div>
  );
};

// ====== Filter chip ======
const FilterChip = ({ label, value, onClear }) => (
  <button className="filter-chip" data-active={value ? "true" : undefined}>
    <span>{label}:</span>
    <span className="chip-value">{value || "すべて"}</span>
    {value && onClear && (
      <span className="chip-clear" onClick={e => { e.stopPropagation(); onClear(); }}>
        <Icon name="close" size={10} />
      </span>
    )}
  </button>
);

// ====== Toast ======
const ToastContext = createContext(null);

const ToastProvider = ({ children }) => {
  const [items, setItems] = useState([]);
  const push = useCallback((msg, opts = {}) => {
    const id = Math.random().toString(36).slice(2);
    setItems(prev => [...prev, { id, msg, ...opts }]);
    setTimeout(() => setItems(prev => prev.filter(t => t.id !== id)), opts.duration || 2500);
  }, []);
  return (
    <ToastContext.Provider value={push}>
      {children}
      <div className="toast-stack">
        {items.map(t => (
          <div key={t.id} className="toast">
            <Icon name={t.icon || "check"} size={14} />
            <span>{t.msg}</span>
          </div>
        ))}
      </div>
    </ToastContext.Provider>
  );
};

const useToast = () => useContext(ToastContext);

// ====== Helper: download CSV ======
function downloadCSV(filename, headers, rows) {
  const esc = (v) => {
    if (v == null) return "";
    const s = String(v);
    if (/[",\n]/.test(s)) return `"${s.replace(/"/g, '""')}"`;
    return s;
  };
  const lines = [
    headers.map(h => esc(h.label)).join(","),
    ...rows.map(r => headers.map(h => esc(h.value(r))).join(",")),
  ];
  const csv = "\uFEFF" + lines.join("\n"); // BOM for Excel JP
  const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url;
  a.download = filename;
  a.click();
  URL.revokeObjectURL(url);
}

// ====== Combobox — searchable single-select for large option sets ======
function ComboBox({ value, onChange, options, placeholder = "選択...", emptyMessage = "該当する候補がありません", clearable = false }) {
  const [open, setOpen] = useState(false);
  const [q, setQ] = useState("");
  const [highlight, setHighlight] = useState(0);
  const ref = useRef(null);
  const inputRef = useRef(null);
  const listRef = useRef(null);

  const selected = options.find(o => o.value === value);

  useEffect(() => {
    if (!open) return;
    const onDown = (e) => { if (ref.current && !ref.current.contains(e.target)) close(); };
    window.addEventListener("mousedown", onDown);
    return () => window.removeEventListener("mousedown", onDown);
  }, [open]);

  useEffect(() => {
    if (open) {
      setQ("");
      setHighlight(0);
      setTimeout(() => inputRef.current?.focus(), 0);
    }
  }, [open]);

  const close = () => { setOpen(false); setQ(""); };

  const filtered = useMemo(() => {
    if (!q) return options;
    const ql = q.toLowerCase();
    return options.filter(o => {
      const haystack = ((o.searchKey || "") + " " + (o.label || "") + " " + (o.sub || "")).toLowerCase();
      return haystack.includes(ql) || (o.label || "").includes(q) || (o.sub || "").includes(q);
    });
  }, [options, q]);

  useEffect(() => {
    if (!open || !listRef.current) return;
    const el = listRef.current.querySelector(`[data-idx="${highlight}"]`);
    if (el) el.scrollIntoView({ block: "nearest" });
  }, [highlight, open]);

  const select = (opt) => {
    onChange(opt.value);
    close();
  };

  const onKeyDown = (e) => {
    if (e.key === "Escape") { e.preventDefault(); close(); return; }
    if (e.key === "ArrowDown") { e.preventDefault(); setHighlight(h => Math.min(filtered.length - 1, h + 1)); }
    if (e.key === "ArrowUp")   { e.preventDefault(); setHighlight(h => Math.max(0, h - 1)); }
    if (e.key === "Enter")     { e.preventDefault(); if (filtered[highlight]) select(filtered[highlight]); }
  };

  return (
    <div ref={ref} style={{ position: "relative" }}>
      <button
        type="button"
        className="input"
        onClick={() => setOpen(o => !o)}
        style={{
          display: "flex", alignItems: "center", gap: 8,
          width: "100%", textAlign: "left", cursor: "pointer",
          padding: "0 26px 0 10px", position: "relative", minHeight: 28,
          background: "var(--surface)",
        }}
      >
        {selected ? (
          <>
            {selected.leading}
            <span style={{ flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", color: "var(--text)" }}>
              {selected.label}
              {selected.sub && <span style={{ color: "var(--text-faint)", fontSize: 11, marginLeft: 6 }}>{selected.sub}</span>}
            </span>
          </>
        ) : (
          <span style={{ flex: 1, color: "var(--text-faint)" }}>{placeholder}</span>
        )}
        {clearable && selected && (
          <span
            onClick={(e) => { e.stopPropagation(); onChange(""); }}
            style={{ position: "absolute", right: 22, top: "50%", transform: "translateY(-50%)", color: "var(--text-faint)", padding: 2 }}
            title="クリア"
          >
            <Icon name="close" size={10} />
          </span>
        )}
        <Icon name="chevron-down" size={10} style={{ position: "absolute", right: 8, top: "50%", transform: "translateY(-50%)", color: "var(--text-faint)" }} />
      </button>
      {open && (
        <div
          className="dropdown"
          style={{
            top: "calc(100% + 4px)", left: 0, right: 0,
            padding: 0, maxHeight: 320,
            overflow: "hidden", display: "flex", flexDirection: "column",
          }}
        >
          <div style={{ padding: 6, borderBottom: "1px solid var(--border)", background: "var(--bg-subtle)", flexShrink: 0 }}>
            <div className="input-search">
              <Icon name="search" size={13} className="search-icon" />
              <input
                ref={inputRef}
                className="input"
                value={q}
                onChange={e => { setQ(e.target.value); setHighlight(0); }}
                onKeyDown={onKeyDown}
                placeholder="検索..."
              />
            </div>
          </div>
          <div ref={listRef} style={{ overflow: "auto", padding: 4, flex: 1 }}>
            {filtered.length === 0 && (
              <div style={{ padding: 20, fontSize: 11.5, color: "var(--text-faint)", textAlign: "center" }}>
                {emptyMessage}
              </div>
            )}
            {filtered.map((opt, i) => (
              <button
                key={opt.value}
                type="button"
                className="dropdown-item"
                data-idx={i}
                style={{
                  background: i === highlight ? "var(--surface-active)" : undefined,
                  padding: "5px 8px",
                  gap: 8,
                  alignItems: "center",
                }}
                onClick={() => select(opt)}
                onMouseEnter={() => setHighlight(i)}
              >
                {opt.leading}
                <span style={{ flex: 1, display: "flex", flexDirection: "column", minWidth: 0 }}>
                  <span style={{ fontSize: 12.5, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{opt.label}</span>
                  {opt.sub && <span className="text-xs faint" style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{opt.sub}</span>}
                </span>
                {opt.value === value && <Icon name="check" size={12} style={{ color: "var(--accent)", flexShrink: 0 }} />}
              </button>
            ))}
          </div>
          <div style={{ borderTop: "1px solid var(--border)", padding: "6px 10px", background: "var(--bg-subtle)", fontSize: 10.5, color: "var(--text-faint)", display: "flex", gap: 12, flexShrink: 0 }}>
            <span><span className="shortcut">↑↓</span> 移動</span>
            <span><span className="shortcut">Enter</span> 選択</span>
            <span><span className="shortcut">Esc</span> 閉じる</span>
            <span style={{ marginLeft: "auto" }}>{filtered.length}件</span>
          </div>
        </div>
      )}
    </div>
  );
}

// Export to window
Object.assign(window, {
  Icon, Button, IconButton, Input, Select, Textarea, Field, SearchInput,
  Badge, Tag, Avatar, AvatarName, Checkbox,
  Modal, ConfirmDialog, DropdownMenu,
  DataTable, Pagination, FilterChip,
  ToastProvider, useToast,
  downloadCSV,
  RolePicker, RoleTags, ComboBox,
});

// ====== Role multi-select picker ======
function RolePicker({ value = [], onChange, roles, placeholder = "クリックして職種を選択" }) {
  const [open, setOpen] = useState(false);
  const [q, setQ] = useState("");
  const ref = useRef(null);

  useEffect(() => {
    if (!open) return;
    const onDown = (e) => { if (ref.current && !ref.current.contains(e.target)) { setOpen(false); setQ(""); } };
    window.addEventListener("mousedown", onDown);
    return () => window.removeEventListener("mousedown", onDown);
  }, [open]);

  const selectedRoles = value.map(id => ROLE_BY_ID[id]).filter(Boolean);
  const available = roles.filter(r =>
    !value.includes(r.id) &&
    (!q || r.name.includes(q) || r.code.toLowerCase().includes(q.toLowerCase()))
  );
  const grouped = {};
  available.forEach(r => { (grouped[r.category] = grouped[r.category] || []).push(r); });

  const toggle = (id) => {
    if (value.includes(id)) onChange(value.filter(v => v !== id));
    else onChange([...value, id]);
  };

  return (
    <div ref={ref} style={{ position: "relative" }}>
      <div
        onClick={() => setOpen(true)}
        style={{
          display: "flex", flexWrap: "wrap", gap: 4,
          padding: 6, minHeight: 32,
          border: "1px solid var(--border)",
          borderRadius: 4, background: "var(--surface)",
          cursor: "text",
        }}
      >
        {selectedRoles.map(r => (
          <button
            key={r.id}
            type="button"
            className="tag"
            style={{ cursor: "pointer", background: "var(--accent-soft)", color: "var(--accent)", borderColor: "var(--accent-border)" }}
            onClick={(e) => { e.stopPropagation(); toggle(r.id); }}
          >
            <span style={{ fontFamily: "var(--font-mono)", fontSize: 10, opacity: 0.7, marginRight: 4 }}>{r.code}</span>
            {r.name}
            <Icon name="close" size={10} style={{ marginLeft: 4 }} />
          </button>
        ))}
        <span style={{ flex: 1, fontSize: 12, color: "var(--text-faint)", padding: "2px 4px", minWidth: 80 }}>
          {selectedRoles.length === 0 ? placeholder : "+ 職種を追加"}
        </span>
      </div>
      {open && (
        <div className="dropdown" style={{ top: "calc(100% + 4px)", left: 0, right: 0, maxHeight: 320, overflow: "auto", padding: 0 }}>
          <div style={{ padding: 6, borderBottom: "1px solid var(--border)", background: "var(--bg-subtle)", position: "sticky", top: 0, zIndex: 1 }}>
            <Input value={q} onChange={setQ} placeholder="職種を検索…" autoFocus />
          </div>
          <div style={{ padding: 4 }}>
            {Object.entries(grouped).map(([cat, rs]) => (
              <div key={cat}>
                <div className="dropdown-label">{cat}</div>
                {rs.map(r => (
                  <button key={r.id} type="button" className="dropdown-item" onClick={() => toggle(r.id)}>
                    <span style={{ fontFamily: "var(--font-mono)", fontSize: 10.5, color: "var(--text-faint)", width: 44 }}>{r.code}</span>
                    <span style={{ flex: 1 }}>{r.name}</span>
                    <span className="text-xs faint">L{r.level}</span>
                  </button>
                ))}
              </div>
            ))}
            {available.length === 0 && (
              <div style={{ padding: 16, fontSize: 11.5, color: "var(--text-faint)", textAlign: "center" }}>
                {q ? "該当する職種がありません" : "すべての職種が選択済みです"}
              </div>
            )}
          </div>
        </div>
      )}
    </div>
  );
}

// ====== Role tags display (compact, used in tables) ======
function RoleTags({ roleIds = [], max = 2 }) {
  const rs = roleIds.map(id => ROLE_BY_ID[id]).filter(Boolean);
  if (rs.length === 0) return <span className="muted">—</span>;
  const shown = rs.slice(0, max);
  const overflow = rs.length - max;
  return (
    <span className="tag-group">
      {shown.map((r, i) => (
        <span
          key={r.id}
          className="tag"
          style={i === 0
            ? { background: "var(--accent-soft)", color: "var(--accent)", borderColor: "var(--accent-border)" }
            : undefined}
          title={`${r.code} · ${r.category}`}
        >
          {r.name}
        </span>
      ))}
      {overflow > 0 && (
        <span className="tag" style={{ background: "transparent" }} title={rs.slice(max).map(r => r.name).join(", ")}>
          +{overflow}
        </span>
      )}
    </span>
  );
}
