/**
 * Primary "Run the ranker" control panel.
 *
 * Three orthogonal ways to feed candidates to the API:
 *   1. Upload your own `.json/.jsonl/.jsonl.gz` (drag-and-drop).
 *   2. Click "Feed sample candidates" → uses the default fixture.
 *   3. Use the `prefilled` prop from the parent (e.g. resume parser).
 *
 * Schema-validation errors are NOT caught here - they bubble up to
 * App.tsx so it can render the dedicated <SchemaDiff/> panel.
 */

import React, { useEffect, useState } from "react";
import { useDropzone } from "react-dropzone";
import { motion, AnimatePresence } from "framer-motion";
import { postRank, SchemaValidationError } from "../api";
import type { RankResponse } from "../types";

interface Props {
  onResult: (r: RankResponse) => void;
  onError: (msg: string) => void;
  onSchemaError: (err: SchemaValidationError) => void;
  prefilled?: File | null;
}

export const RunPanel: React.FC<Props> = ({
  onResult,
  onError,
  onSchemaError,
  prefilled,
}) => {
  const [candidates, setCandidates] = useState<File | null>(null);
  const [jd, setJd] = useState<File | null>(null);
  const [topK, setTopK] = useState<number>(10);
  const [busy, setBusy] = useState(false);
  const [mode, setMode] = useState<"upload" | "sample">("upload");

  useEffect(() => {
    if (prefilled) {
      setCandidates(prefilled);
      setMode("upload");
    }
  }, [prefilled]);

  async function runUpload() {
    if (!candidates) return;
    await runRequest({ candidates, jd, topK });
  }

  async function runSample() {
    setMode("sample");
    await runRequest({ useSample: true, jd, topK });
  }

  async function runRequest(opts: {
    candidates?: File | null;
    jd?: File | null;
    topK?: number;
    useSample?: boolean;
  }) {
    setBusy(true);
    try {
      const res = await postRank(opts);
      onResult(res);
    } catch (e) {
      if (e instanceof SchemaValidationError) {
        onSchemaError(e);
      } else {
        onError(e instanceof Error ? e.message : String(e));
      }
    } finally {
      setBusy(false);
    }
  }

  // NOTE: many browsers report `.jsonl` as `application/octet-stream` and
  // some report `.gz` as `application/x-gzip`. Listing the extensions under
  // a wildcard MIME key tells react-dropzone to match by extension first,
  // which is what we want.
  const candidateDrop = useDropzone({
    onDrop: (files) => files[0] && setCandidates(files[0]),
    multiple: false,
    accept: {
      "application/json": [".json"],
      "application/x-ndjson": [".jsonl"],
      "application/octet-stream": [".jsonl", ".gz"],
      "application/gzip": [".gz"],
      "application/x-gzip": [".gz"],
    },
    // 600 MB - the official candidates.jsonl is ~480 MB.
    maxSize: 600 * 1024 * 1024,
  });


  // JD upload — accept text, markdown, .docx and .pdf. The official Redrob
  // bundle ships `job_description.docx`, so this must Just Work.
  const jdDrop = useDropzone({
    onDrop: (files) => files[0] && setJd(files[0]),
    multiple: false,
    accept: {
      "text/plain": [".txt", ".md"],
      "text/markdown": [".md"],
      "application/vnd.openxmlformats-officedocument.wordprocessingml.document": [".docx"],
      "application/pdf": [".pdf"],
      "application/octet-stream": [".docx", ".pdf", ".txt", ".md"],
    },
    maxSize: 10 * 1024 * 1024,
  });


  return (
    <section className="card-glow p-6 sm:p-8 transition-transform duration-500 hover:-translate-y-1">
      <header className="mb-5">
        <h2 className="text-sm uppercase tracking-widest text-bone-300 flex items-center gap-2">
          <span className="inline-block w-2 h-2 rounded-full bg-emerald-400 animate-pulse" />
          Run the ranker
        </h2>
        <p className="text-xs text-bone-400 mt-2 max-w-2xl">
          Drop your candidates file, or click <em>"Feed sample candidates"</em> to
          explore with the default 50-row fixture. Every upload is validated
          against the official Redrob schema first - mismatches surface as a
          git diff style report, not a stack trace.
        </p>

      </header>

      <div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-5">
        <DropField
          label="Candidates (.json / .jsonl / .jsonl.gz)"
          dropzone={candidateDrop}
          file={candidates}
          onClear={() => setCandidates(null)}
          accent="bone"
        />
        <DropField
          label="Job description (.txt / .md / .docx / .pdf) - optional"
          dropzone={jdDrop}
          file={jd}
          onClear={() => setJd(null)}
          accent="bone"
          hint={
            jd
              ? `Will rank against your uploaded JD`
              : `If empty, the default Senior-AI-Engineer JD is used`
          }
        />

      </div>

      <div className="flex flex-wrap items-center gap-4 mb-6">
        <label className="text-xs uppercase tracking-widest text-bone-300">Top‑K</label>
        <input
          type="number"
          min={1}
          max={100}
          value={topK}
          onChange={(e) => setTopK(Math.min(100, Math.max(1, Number(e.target.value) || 1)))}
          className="w-24 bg-ink-800/80 border hairline px-3 py-1.5 text-sm font-mono focus:outline-none focus:border-bone-50 transition-colors"
        />
      </div>

      <div className="flex flex-wrap gap-3">
        <button
          className="btn-primary disabled:opacity-50 group"
          disabled={busy || !candidates}
          onClick={runUpload}
        >
          {busy && mode === "upload" ? (
            <>
              <Spinner /> Ranking…
            </>
          ) : (
            <>
              Rank uploaded pool
              <span className="transition-transform group-hover:translate-x-0.5">→</span>
            </>
          )}
        </button>

        <button
          className="btn-ghost relative overflow-hidden disabled:opacity-50 group"
          disabled={busy}
          onClick={runSample}
          title="Use the default 50-row sample to explore the UI"
        >
          {busy && mode === "sample" ? (
            <>
              <Spinner /> Ranking sample…
            </>
          ) : (
            <>
              <SparkleIcon /> Feed sample candidates
            </>
          )}
        </button>
      </div>

      <AnimatePresence>
        {candidates && (
          <motion.p
            key={candidates.name}
            initial={{ opacity: 0, y: 4 }}
            animate={{ opacity: 1, y: 0 }}
            exit={{ opacity: 0 }}
            className="mt-4 text-[11px] text-bone-400 font-mono"
          >
            Ready: <span className="text-bone-200">{candidates.name}</span> (
            {(candidates.size / 1024).toFixed(1)} KB)
          </motion.p>
        )}
      </AnimatePresence>
    </section>
  );
};

// ───────────────────────── helpers ─────────────────────────────────────────

const Spinner: React.FC = () => (
  <span className="inline-block w-3 h-3 border-2 border-current border-t-transparent rounded-full animate-spin" />
);

const SparkleIcon: React.FC = () => (
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
    <path d="M12 3l1.7 4.6 4.6 1.7-4.6 1.7L12 15.6 10.3 11l-4.6-1.7 4.6-1.7L12 3z" />
    <path d="M19 14l1 2.6 2.6 1-2.6 1-1 2.6-1-2.6-2.6-1 2.6-1 1-2.6z" />
  </svg>
);

interface DropFieldProps {
  label: string;
  file: File | null;
  onClear: () => void;
  dropzone: ReturnType<typeof useDropzone>;
  accent: "bone" | "red";
  hint?: string;
}

const DropField: React.FC<DropFieldProps> = ({ label, file, onClear, dropzone, hint }) => {
  const { getRootProps, getInputProps, isDragActive, isDragReject } = dropzone;
  return (
    <div>
      <div className="text-[11px] uppercase tracking-widest text-bone-400 mb-2">{label}</div>

      <div
        {...getRootProps()}
        className={`relative card flex items-center justify-between px-4 py-3 cursor-pointer transition-all ${
          isDragReject
            ? "border-red-400/60 bg-red-500/10"
            : isDragActive
            ? "border-bone-50/80 bg-bone-50/10 -translate-y-0.5"
            : "hover:border-bone-50/40"
        }`}
      >
        <input {...getInputProps()} />
        <span className="font-mono text-xs truncate text-bone-200 max-w-[60%]">
          {file ? file.name : isDragActive ? "drop to attach" : "drag or click to attach"}
        </span>
        {file ? (
          <button
            type="button"
            onClick={(e) => {
              e.stopPropagation();
              onClear();
            }}
            className="btn-ghost text-[10px] px-2 py-1"
          >
            clear
          </button>
        ) : (
          <span className="btn-ghost pointer-events-none">Browse</span>
        )}
      </div>
      {hint && (
        <div className="text-[10px] text-bone-400 mt-1.5 italic font-mono">
          {hint}
        </div>
      )}
    </div>
  );
};


export default RunPanel;
