// ---------------------------------------------------------------------------
// customer-grid / SelectFromFile.tsx — WAVE 21 item 11 (R10, contract C5).
//
// "Select from file…": paste a list or upload a sheet, say which column holds
// the names, and the grid ticks those records.
//
// The dialog decides NOTHING. Parsing a paste, matching, and the three-bucket
// result all live in `fileSelect.ts` where a gate runs them under node; this is
// the renderer and the file-picker chrome around them.
// ---------------------------------------------------------------------------

import { useMemo, useRef, useState } from "react";
import { uploadTabular } from "./apiBridge";
import type { TabularUpload } from "./apiBridge";
import { matchRowsByValue, matchSummary, parsePastedList } from "./fileSelect";
import type { MatchResult, MatchRow } from "./fileSelect";
import type { Field } from "./types";

/** The kinds a name can plausibly live in. A rating or a checkbox column is not
 *  something anyone pastes a list of, and offering all 27 field types makes the
 *  useful three hard to find. */
const MATCHABLE = new Set([
  "text", "longtext", "select", "multiselect", "email", "url", "phone", "int",
  "currency", "number", "date", "user", "automation", "formula",
]);

export interface SelectFromFileProps {
  /** Columns the user may match ON — the grid's fields, in grid order. */
  fields: Field[];
  /** The identity column: the default grid column, and the one that makes this
   *  feature obvious ("paste customer names"). */
  primaryKey: string;
  /** ⚠ The rows the VIEW MATCHED — never the painted slice. See fileSelect.ts. */
  inView: MatchRow[];
  /** Every row in this table, for the "in the table but not in this view" bucket. */
  wholeTable: MatchRow[];
  /** The view being selected in, named in the dialog so the counts have a subject. */
  viewName: string;
  onSelect: (pids: number[]) => void;
  onClose: () => void;
}

export default function SelectFromFile({
  fields,
  primaryKey,
  inView,
  wholeTable,
  viewName,
  onSelect,
  onClose,
}: SelectFromFileProps) {
  const matchable = useMemo(
    () => fields.filter((f) => MATCHABLE.has(f.type) || f.key === primaryKey),
    [fields, primaryKey]
  );
  const [gridKey, setGridKey] = useState(primaryKey);
  const [paste, setPaste] = useState("");
  const [upload, setUpload] = useState<TabularUpload | null>(null);
  const [fileName, setFileName] = useState("");
  const [fileCol, setFileCol] = useState("");
  const [busy, setBusy] = useState(false);
  const [result, setResult] = useState<MatchResult | null>(null);
  const fileRef = useRef<HTMLInputElement>(null);

  const pasted = useMemo(() => parsePastedList(paste), [paste]);
  // The upload wins when there is one: the user picked a file most recently, and
  // a dialog that matched a stale paste behind an uploaded sheet would be
  // answering a question nobody asked.
  const values = upload ? upload.values[fileCol] ?? [] : pasted.values;
  const canRun = values.length > 0 && !!gridKey;

  const run = () => {
    const r = matchRowsByValue(values, gridKey, inView, wholeTable);
    setResult(r);
    onSelect(r.pids);
  };

  const takeFile = async (file: File | undefined) => {
    if (!file) return;
    setBusy(true);
    setResult(null);
    const parsed = await uploadTabular(file);
    setBusy(false);
    if (!parsed) return;              // the bridge has already said why
    setUpload(parsed);
    setFileName(file.name);
    setFileCol(parsed.columns[0] ?? "");
    setPaste("");
  };

  /** The misses, ready to paste back into whatever the list came from. Every
   *  value, never a "…and 40 more" — the whole point of listing them is that the
   *  user has to go and fix them. */
  const copyList = (list: string[]) => {
    void navigator.clipboard?.writeText(list.join("\n"));
  };

  return (
    <div className="cg-cat-modal-wrap" role="dialog" aria-modal="true"
         aria-label="Select records from a file">
      <div className="cg-cat-modal cg-sff">
        <h3>Select records from a list</h3>
        <p>
          Paste a list or upload a sheet, and the matching records in{" "}
          <strong>{viewName}</strong> are ticked. Values are compared exactly, ignoring
          case and surrounding spaces.
        </p>

        <div className="cg-sff-row">
          <label className="cg-sff-label" htmlFor="cg-sff-paste">Paste a list</label>
          <textarea
            id="cg-sff-paste"
            className="cg-sff-area"
            value={paste}
            placeholder={"One value per line"}
            onChange={(e) => {
              setPaste(e.target.value);
              setUpload(null);
              setFileName("");
              setResult(null);
            }}
          />
        </div>

        <div className="cg-sff-row">
          <span className="cg-sff-label">…or upload</span>
          <div className="cg-sff-file">
            <input
              ref={fileRef}
              type="file"
              accept=".xlsx,.csv"
              className="cg-sff-input"
              onChange={(e) => void takeFile(e.target.files?.[0])}
            />
            {fileName ? <span className="cg-sff-note">{fileName}</span> : null}
            {busy ? <span className="cg-sff-note">Reading…</span> : null}
          </div>
        </div>

        {/* The column pair. The file side exists only for an upload — a pasted
            list IS one column, and a picker offering one option is furniture. */}
        <div className="cg-sff-pair">
          {upload ? (
            <label className="cg-sff-pick">
              <span className="cg-sff-label">Column in the file</span>
              <select
                className="cg-input"
                value={fileCol}
                onChange={(e) => {
                  setFileCol(e.target.value);
                  setResult(null);
                }}
              >
                {upload.columns.map((c) => (
                  <option key={c} value={c}>{c}</option>
                ))}
              </select>
            </label>
          ) : null}
          <label className="cg-sff-pick">
            <span className="cg-sff-label">Column in the grid</span>
            <select
              className="cg-input"
              value={gridKey}
              onChange={(e) => {
                setGridKey(e.target.value);
                setResult(null);
              }}
            >
              {matchable.map((f) => (
                <option key={f.key} value={f.key}>{f.label}</option>
              ))}
            </select>
          </label>
        </div>

        {/* ── every disclosure the inputs owe, before the button ─────────────── */}
        {upload?.truncated ? (
          <p className="cg-sff-warn">
            This file is longer than the {(20000).toLocaleString()}-value limit — only the
            first {(20000).toLocaleString()} values in each column were read. Anything past
            that is not being matched.
          </p>
        ) : null}
        {!upload && pasted.extraColumns ? (
          <p className="cg-sff-warn">
            The pasted text has more than one column — only the first is being matched.
          </p>
        ) : null}
        {!upload && pasted.blanks > 0 ? (
          <p className="cg-sff-note">
            {pasted.blanks.toLocaleString()} empty line{pasted.blanks === 1 ? "" : "s"} skipped.
          </p>
        ) : null}
        {values.length > 0 ? (
          <p className="cg-sff-note">
            {values.length.toLocaleString()} value{values.length === 1 ? "" : "s"} to match.
          </p>
        ) : null}

        {result ? (
          <div className="cg-sff-result">
            <strong>{matchSummary(result)}</strong>
            {result.outsideView.length ? (
              <div className="cg-sff-misses">
                <span>
                  In this table, but not shown by <strong>{viewName}</strong> — clear the
                  view&rsquo;s filters to reach them:
                </span>
                <textarea readOnly className="cg-sff-area cg-sff-missarea"
                          value={result.outsideView.join("\n")} />
                <button type="button" className="cg-btn"
                        onClick={() => copyList(result.outsideView)}>
                  Copy these
                </button>
              </div>
            ) : null}
            {result.missing.length ? (
              <div className="cg-sff-misses">
                <span>Not found in this table at all:</span>
                <textarea readOnly className="cg-sff-area cg-sff-missarea"
                          value={result.missing.join("\n")} />
                <button type="button" className="cg-btn"
                        onClick={() => copyList(result.missing)}>
                  Copy these
                </button>
              </div>
            ) : null}
          </div>
        ) : null}

        <div className="cg-sff-actions">
          <button type="button" className="cg-btn cg-btn--primary" disabled={!canRun || busy}
                  onClick={run}>
            {result ? "Select again" : "Select"}
          </button>
          <button type="button" className="cg-btn" onClick={onClose}>
            {result ? "Done" : "Cancel"}
          </button>
        </div>
      </div>
    </div>
  );
}
