"use client";

import { mutationCsrfHeaders } from "@/lib/mutation-csrf.client";
import { toastApiError } from "@/lib/toast-api-error";
import { ChevronDown, ChevronRight, FileText, Loader2, Plus, Upload, X } from "lucide-react";
import * as React from "react";
import { toast } from "sonner";

import { Button } from "@/components/ui/button";
import {
  ORDER_BLOTTER_COLUMNS,
  ORDER_BLOTTER_SECTION_TITLES,
  registrySelectOptionsForKey,
  type OrderBlotterSectionId,
} from "@/lib/order-blotter/column-registry";
import { cn } from "@/lib/utils";

const ACCEPTED_EXTENSIONS = [".eml", ".msg", ".pdf", ".txt"];
const ACCEPT_ATTR = ".eml,.msg,.pdf,.txt,message/rfc822,application/pdf,text/plain";

const FIELD_LABELS = Object.fromEntries(
  ORDER_BLOTTER_COLUMNS.map((column) => [column.key, column.label]),
) as Record<string, string>;

export type OcrApiResponse = {
  status?: string;
  message?: string;
  fields?: Record<string, string>;
  fields_by_section?: Record<string, Record<string, string>>;
  field_count?: number;
  file_errors?: Array<{ filename?: string; error?: string }>;
  analysis?: {
    flow?: string;
    flow_label?: string;
    message_count?: number;
    section_ids?: string[];
  };
};

type OrderBlotterDocumentsPanelProps = {
  ocrPath: string;
  files: File[];
  onFilesChange: (files: File[]) => void;
  result: OcrApiResponse | null;
  onResultChange: (result: OcrApiResponse | null) => void;
  onApplyFields: (fields: Record<string, string>, overwrite: boolean) => number;
  onJumpToSection?: (sectionId: OrderBlotterSectionId) => void;
};

export function OrderBlotterDocumentsPanel({
  ocrPath,
  files,
  onFilesChange,
  result,
  onResultChange,
  onApplyFields,
  onJumpToSection,
}: OrderBlotterDocumentsPanelProps) {
  const inputRef = React.useRef<HTMLInputElement>(null);
  const [dragging, setDragging] = React.useState(false);
  const [processing, setProcessing] = React.useState(false);
  const [overwrite, setOverwrite] = React.useState(false);
  const [openSectionId, setOpenSectionId] = React.useState<string | null>(null);

  const extractedFields = React.useMemo(() => collectExtractedOcrFields(result), [result]);
  const extractedCount = Object.keys(extractedFields).length;
  const sectionIds = (result?.analysis?.section_ids ??
    Object.keys(result?.fields_by_section ?? {})) as string[];
  const fieldsBySection = result?.fields_by_section ?? {};

  const onDragOver = (event: React.DragEvent) => {
    event.preventDefault();
    setDragging(true);
  };

  const onDragLeave = () => setDragging(false);

  const onDrop = (event: React.DragEvent) => {
    event.preventDefault();
    setDragging(false);
    if (event.dataTransfer.files?.length) {
      addFiles(event.dataTransfer.files);
    }
  };

  const addFiles = (incoming: FileList | File[]) => {
    const next = Array.from(incoming).filter((file) => {
      const name = file.name.toLowerCase();
      return ACCEPTED_EXTENSIONS.some((ext) => name.endsWith(ext));
    });
    if (!next.length) {
      toast.error("Use .eml, .msg, .pdf, or .txt files.");
      return;
    }
    const merged = [...files];
    for (const file of next) {
      if (!merged.some((existing) => existing.name === file.name && existing.size === file.size)) {
        merged.push(file);
      }
    }
    onFilesChange(merged);
    onResultChange(null);
    setOpenSectionId(null);
  };

  const removeFile = (index: number) => {
    onFilesChange(files.filter((_, i) => i !== index));
    onResultChange(null);
    setOpenSectionId(null);
  };

  const handleExtract = async () => {
    if (!files.length) {
      toast.error("Add at least one document first.");
      return;
    }
    setProcessing(true);
    try {
      const formData = new FormData();
      for (const file of files) {
        formData.append("documents[]", file, file.name);
      }
      const response = await fetch(ocrPath, {
        method: "POST",
        credentials: "same-origin",
        body: formData,
        headers: { Accept: "application/json", ...mutationCsrfHeaders(ocrPath) },
      });
      const payload = (await response.json().catch(() => null)) as OcrApiResponse | null;
      if (!response.ok || !payload || payload.status !== "success") {
        throw new Error(payload?.message || "OCR extraction failed");
      }
      onResultChange(payload);
      const ids = payload.analysis?.section_ids ?? Object.keys(payload.fields_by_section ?? {});
      setOpenSectionId(ids[0] ?? null);
      const count =
        payload.field_count ?? Object.keys(collectExtractedOcrFields(payload)).length;
      toast.success(
        count > 0
          ? `Ready to apply ${count} field${count === 1 ? "" : "s"}`
          : "OCR finished but no mapped fields were found",
      );
    } catch (error) {
      toastApiError(error, "OCR extraction failed");
    } finally {
      setProcessing(false);
    }
  };

  const handleApply = () => {
    if (!extractedCount) {
      toast.error("Nothing to apply — extract documents first.");
      return;
    }
    const applied = onApplyFields(extractedFields, overwrite);
    const firstSection = sectionIds.find((id) =>
      Boolean(ORDER_BLOTTER_SECTION_TITLES[id as OrderBlotterSectionId]),
    );
    if (firstSection && onJumpToSection) {
      window.setTimeout(() => {
        onJumpToSection(firstSection as OrderBlotterSectionId);
      }, 0);
    }
    if (applied <= 0) {
      toast.info(
        "No new fields applied — turn on overwrite to replace values already on the form.",
      );
      return;
    }
    toast.success(`Applied ${applied} field${applied === 1 ? "" : "s"} to the form`);
  };

  return (
    <div className="grid gap-6 lg:grid-cols-[minmax(0,1fr)_minmax(0,1.15fr)] lg:items-stretch lg:gap-8">
      <div className="flex flex-col gap-4">
        <section className="space-y-2">
          <header className="flex items-baseline justify-between gap-2">
            <h3 className="text-sm font-semibold text-foreground">Add documents</h3>
            <p className="text-[11px] text-muted-foreground">.eml · .msg · .pdf · .txt</p>
          </header>

          {files.length === 0 ? (
            <div
              className={cn(
                "flex min-h-[11rem] flex-1 cursor-pointer flex-col items-center justify-center gap-2 rounded-lg border border-dashed px-4 py-8 transition-colors",
                dragging
                  ? "border-primary/50 bg-muted"
                  : "border-border bg-muted/50 hover:bg-muted/80",
              )}
              onDragOver={onDragOver}
              onDragLeave={onDragLeave}
              onDrop={onDrop}
              onClick={() => inputRef.current?.click()}
              onKeyDown={(event) => {
                if (event.key === "Enter" || event.key === " ") {
                  event.preventDefault();
                  inputRef.current?.click();
                }
              }}
              role="button"
              tabIndex={0}
            >
              <Upload className="size-6 text-muted-foreground" />
              <p className="text-sm font-medium text-foreground">Drop files here or browse</p>
            </div>
          ) : (
            <div
              className={cn(
                "rounded-lg border border-border bg-card",
                dragging && "ring-2 ring-ring/40",
              )}
              onDragOver={onDragOver}
              onDragLeave={onDragLeave}
              onDrop={onDrop}
            >
              <ul className="divide-y divide-border">
                {files.map((file, index) => (
                  <li
                    key={`${file.name}-${file.size}-${index}`}
                    className="flex items-center gap-2 px-3 py-2 text-xs"
                  >
                    <FileText className="size-3.5 shrink-0 text-muted-foreground" />
                    <span className="min-w-0 flex-1 truncate text-foreground">{file.name}</span>
                    <span className="shrink-0 tabular-nums text-muted-foreground">
                      {formatBytes(file.size)}
                    </span>
                    <button
                      type="button"
                      className="rounded p-0.5 text-muted-foreground hover:bg-muted hover:text-foreground"
                      onClick={() => removeFile(index)}
                      aria-label={`Remove ${file.name}`}
                    >
                      <X className="size-3.5" />
                    </button>
                  </li>
                ))}
              </ul>
              <div className="border-t border-border px-2 py-1.5">
                <button
                  type="button"
                  className="inline-flex items-center gap-1 rounded px-2 py-1 text-[11px] font-medium text-muted-foreground hover:bg-muted hover:text-foreground"
                  onClick={() => inputRef.current?.click()}
                >
                  <Plus className="size-3" />
                  Add more
                </button>
              </div>
            </div>
          )}

          <input
            ref={inputRef}
            type="file"
            className="hidden"
            accept={ACCEPT_ATTR}
            multiple
            onChange={(event) => {
              if (event.target.files?.length) {
                addFiles(event.target.files);
                event.target.value = "";
              }
            }}
          />
        </section>

        <Button
          type="button"
          size="sm"
          className="h-9 w-full text-sm"
          disabled={!files.length || processing}
          onClick={() => void handleExtract()}
        >
          {processing ? (
            <>
              <Loader2 className="mr-1.5 size-3.5 animate-spin" />
              Extracting…
            </>
          ) : (
            "Run OCR"
          )}
        </Button>
      </div>

      <div className="flex min-h-[16rem] min-w-0 flex-col gap-3 lg:border-l lg:border-border lg:pl-8">
        <header className="flex flex-wrap items-start justify-between gap-3">
          <div className="min-w-0">
            <h3 className="text-sm font-semibold text-foreground">Review & apply</h3>
            {result ? (
              <p className="mt-0.5 text-[11px] text-muted-foreground">
                {extractedCount} field{extractedCount === 1 ? "" : "s"}
                {result.analysis?.message_count
                  ? ` · ${result.analysis.message_count} message${result.analysis.message_count === 1 ? "" : "s"}`
                  : ""}
                {result.analysis?.flow_label || result.analysis?.flow
                  ? ` · ${result.analysis.flow_label || result.analysis.flow}`
                  : ""}
              </p>
            ) : (
              <p className="mt-0.5 text-[11px] text-muted-foreground">
                Run OCR to preview mapped fields here.
              </p>
            )}
          </div>
          {result ? (
            <div className="flex flex-col items-stretch gap-1.5 sm:items-end">
              <Button
                type="button"
                size="sm"
                className="h-9 bg-emerald-700 px-4 text-sm text-white hover:bg-emerald-800"
                disabled={extractedCount === 0}
                onClick={handleApply}
              >
                Apply to form
              </Button>
              <label className="flex items-center gap-1.5 text-[11px] text-muted-foreground">
                <input
                  type="checkbox"
                  className="size-3 rounded border-input"
                  checked={overwrite}
                  onChange={(event) => setOverwrite(event.target.checked)}
                />
                Overwrite filled fields
              </label>
            </div>
          ) : null}
        </header>

        {!result ? (
          <div className="flex flex-1 items-center justify-center rounded-lg border border-dashed border-border bg-muted/50 px-6 py-10 text-center">
            <p className="max-w-[18rem] text-xs leading-relaxed text-muted-foreground">
              Extracted values will show in this column. Apply sends them into the form steps.
            </p>
          </div>
        ) : (
          <>
            {result.file_errors?.length ? (
              <ul className="rounded-md border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-[11px] text-amber-800 dark:text-amber-300">
                {result.file_errors.map((err, index) => (
                  <li key={`${err.filename}-${index}`}>
                    {err.filename || "File"}: {err.error || "failed"}
                  </li>
                ))}
              </ul>
            ) : null}

            {sectionIds.length ? (
              <div className="overflow-hidden rounded-lg border border-border bg-card">
                {sectionIds.map((sectionId, index) => {
                  const sectionFields = fieldsBySection[sectionId] ?? {};
                  const entries = Object.entries(sectionFields).filter(
                    ([, value]) => String(value ?? "").trim() !== "",
                  );
                  if (!entries.length) return null;
                  const label =
                    ORDER_BLOTTER_SECTION_TITLES[sectionId as OrderBlotterSectionId] ??
                    sectionId;
                  const open = openSectionId === sectionId;
                  return (
                    <div
                      key={sectionId}
                      className={cn(index > 0 && "border-t border-border")}
                    >
                      <button
                        type="button"
                        className="flex w-full items-center gap-2 px-3 py-2.5 text-left hover:bg-muted/50"
                        onClick={() =>
                          setOpenSectionId((prev) => (prev === sectionId ? null : sectionId))
                        }
                      >
                        {open ? (
                          <ChevronDown className="size-3.5 shrink-0 text-muted-foreground" />
                        ) : (
                          <ChevronRight className="size-3.5 shrink-0 text-muted-foreground" />
                        )}
                        <span className="min-w-0 flex-1 text-xs font-semibold text-foreground">
                          {titleCaseSection(label)}
                        </span>
                        <span className="rounded bg-muted px-1.5 py-0.5 text-[10px] font-medium tabular-nums text-muted-foreground">
                          {entries.length}
                        </span>
                      </button>
                      {open ? (
                        <dl className="border-t border-border bg-muted/30 px-3 py-2">
                          {entries.map(([key, value]) => (
                            <div
                              key={key}
                              className="grid gap-0.5 py-1.5 sm:grid-cols-[8rem_1fr] sm:gap-3"
                            >
                              <dt className="text-[10px] font-medium uppercase tracking-wide text-muted-foreground">
                                {FIELD_LABELS[key] || key}
                              </dt>
                              <dd className="break-words text-xs text-foreground">
                                {formatFieldDisplay(key, value)}
                              </dd>
                            </div>
                          ))}
                        </dl>
                      ) : null}
                    </div>
                  );
                })}
              </div>
            ) : (
              <p className="text-xs text-muted-foreground">
                No fields were mapped from these documents.
              </p>
            )}
          </>
        )}
      </div>
    </div>
  );
}

function titleCaseSection(label: string): string {
  return label.toLowerCase().replace(/\b\w/g, (char) => char.toUpperCase());
}

function formatFieldDisplay(key: string, value: string): string {
  const opts = registrySelectOptionsForKey(key);
  return opts?.[value] ?? value;
}

function formatBytes(bytes: number): string {
  if (bytes < 1024) return `${bytes} B`;
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}

function collectExtractedOcrFields(result: OcrApiResponse | null): Record<string, string> {
  if (!result) return {};
  const out: Record<string, string> = {};

  const assign = (key: string, value: unknown) => {
    const trimmed = value == null ? "" : String(value).trim();
    if (!key || !trimmed) return;
    out[key] = trimmed;
  };

  if (result.fields_by_section && typeof result.fields_by_section === "object") {
    for (const sectionFields of Object.values(result.fields_by_section)) {
      if (!sectionFields || typeof sectionFields !== "object") continue;
      for (const [key, value] of Object.entries(sectionFields)) {
        assign(key, value);
      }
    }
  }

  if (result.fields && typeof result.fields === "object" && !Array.isArray(result.fields)) {
    for (const [key, value] of Object.entries(result.fields)) {
      assign(key, value);
    }
  }

  return out;
}
