"use client";

import { CircleAlertIcon, FileUpIcon, Undo2Icon, XIcon } from "lucide-react";
import { useRef, useState } from "react";
import { toast } from "sonner";

import { ConfirmDialog } from "@/components/admin/confirm-dialog";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner";
import { importMarkdown } from "@/features/blogs/actions";
import type { MarkdownImportResult } from "@/features/blogs/markdown-import";
import type { BlogFormValues } from "@/features/blogs/schemas";
import { cn } from "@/lib/utils";

import { useBlogEditor } from "./editor-context";

/** Matches MARKDOWN_MAX_CHARS on the server, checked here before uploading. */
const MAX_FILE_BYTES = 900_000;

type Imported = {
  fileName: string;
  result: MarkdownImportResult;
  previous: BlogFormValues;
};

/** "2026-11-02" or "2026-11-02T09:30" in the editor's time zone, or an ISO string, to ISO. */
function toIso(value: string) {
  const local = /^\d{4}-\d{2}-\d{2}$/.test(value) ? `${value}T09:00` : value;
  const date = new Date(local);
  return Number.isNaN(date.getTime()) ? null : date.toISOString();
}

/**
 * "Import .md": reads a blog Markdown file and fills the editor with it. The
 * hook owns the hidden file input, the replace confirmation and the report.
 */
export function useMarkdownImport() {
  const { values, applyValues, meta, setSlugTouched } = useBlogEditor();
  const inputRef = useRef<HTMLInputElement>(null);
  const [busy, setBusy] = useState(false);
  const [awaitingConfirm, setAwaitingConfirm] = useState<{
    fileName: string;
    result: MarkdownImportResult;
  } | null>(null);
  const [imported, setImported] = useState<Imported | null>(null);

  const hasContent = Boolean(values.title.trim() || values.content);

  const undo = (previous: BlogFormValues) => {
    applyValues(previous);
    setImported(null);
    toast.success("Import undone");
  };

  const apply = (fileName: string, result: MarkdownImportResult) => {
    const previous = values;
    const patch: Partial<BlogFormValues> = { ...result.values };
    const warnings = [...result.report.warnings];

    if (result.publishDate) {
      const iso = toIso(result.publishDate);
      if (iso) patch.publishedAt = iso;
    }

    // A live article's address only changes on purpose, never as a side effect of an import.
    const isLive = meta !== null && meta.status !== "DRAFT";
    if (isLive && patch.slug !== undefined && patch.slug !== values.slug) {
      warnings.unshift(
        `The URL slug was kept as “${values.slug}” because the article is live. Change it under the title if you mean to; the old address will redirect.`,
      );
      delete patch.slug;
    }

    applyValues(patch);
    if (patch.slug !== undefined) setSlugTouched(result.slugFromFile);

    setImported({
      fileName,
      result: { ...result, report: { ...result.report, warnings } },
      previous,
    });
    window.scrollTo({ top: 0, behavior: "smooth" });
    toast.success(`Imported ${fileName}`, {
      action: { label: "Undo", onClick: () => undo(previous) },
    });
  };

  const onFile = async (file: File) => {
    if (!/\.(md|markdown|mdown|txt)$/i.test(file.name)) {
      toast.error("Choose a Markdown file (.md).");
      return;
    }
    if (file.size > MAX_FILE_BYTES) {
      toast.error(
        "This file is too large to import. Split it or remove embedded images.",
      );
      return;
    }

    setBusy(true);
    const markdown = await file.text();
    const response = await importMarkdown({
      markdown,
      blogId: meta?.id ?? null,
    }).catch(() => ({
      ok: false as const,
      error: "Couldn't reach the server. Check your connection and try again.",
    }));
    setBusy(false);

    if (!response.ok) {
      toast.error(response.error);
      return;
    }
    if (hasContent)
      setAwaitingConfirm({ fileName: file.name, result: response.data });
    else apply(file.name, response.data);
  };

  const element = (
    <>
      <input
        ref={inputRef}
        type="file"
        accept=".md,.markdown,.mdown,.txt,text/markdown,text/plain"
        hidden
        onChange={(event) => {
          const file = event.target.files?.[0];
          event.target.value = "";
          if (file) void onFile(file);
        }}
      />
      <ConfirmDialog
        open={awaitingConfirm !== null}
        onOpenChange={(open) => !open && setAwaitingConfirm(null)}
        title={`Replace this article with ${awaitingConfirm?.fileName ?? "the file"}?`}
        description={
          meta && meta.status !== "DRAFT"
            ? "The title, excerpt, body, FAQs and SEO text are replaced. Readers keep seeing the live version until you click Update, and you can undo the import."
            : "The title, excerpt, body, FAQs and SEO text are replaced. You can undo the import straight after."
        }
        confirmLabel="Replace"
        onConfirm={() => {
          if (awaitingConfirm)
            apply(awaitingConfirm.fileName, awaitingConfirm.result);
          setAwaitingConfirm(null);
        }}
      />
    </>
  );

  return {
    open: () => inputRef.current?.click(),
    busy,
    element,
    imported,
    dismiss: () => setImported(null),
    undo: () => imported && undo(imported.previous),
  };
}

const COUNT_LABELS: [key: string, singular: string, plural: string][] = [
  ["words", "word", "words"],
  ["headings", "heading", "headings"],
  ["tables", "table", "tables"],
  ["lists", "list", "lists"],
  ["images", "image", "images"],
  ["links", "link", "links"],
  ["faqs", "FAQ", "FAQs"],
];

/** What the import did, shown above the article until dismissed. */
export function MarkdownImportReport({
  imported,
  onUndo,
  onDismiss,
}: {
  imported: Imported;
  onUndo: () => void;
  onDismiss: () => void;
}) {
  const { meta } = useBlogEditor();
  const { report } = imported.result;
  const summary = COUNT_LABELS.filter(([key]) => report.counts[key])
    .map(
      ([key, one, many]) =>
        `${report.counts[key].toLocaleString()} ${report.counts[key] === 1 ? one : many}`,
    )
    .join(" · ");
  const hasWarnings = report.warnings.length > 0;

  return (
    <div
      role="status"
      className={cn(
        "mb-5 rounded-xl border px-4 py-3.5 text-sm",
        hasWarnings
          ? "border-warning/30 bg-warning-soft"
          : "border-info/20 bg-info-soft",
      )}
    >
      <div className="flex items-start gap-3">
        <FileUpIcon
          className={cn(
            "mt-0.5 size-4 shrink-0",
            hasWarnings ? "text-warning" : "text-info",
          )}
        />
        <div className="min-w-0 flex-1">
          <p className="font-medium text-foreground">
            Imported {imported.fileName}
          </p>
          {summary && <p className="text-muted-foreground">{summary}</p>}
        </div>
        <Button type="button" size="sm" variant="ghost" onClick={onUndo}>
          <Undo2Icon />
          Undo
        </Button>
        <Button
          type="button"
          size="icon-sm"
          variant="ghost"
          aria-label="Dismiss import summary"
          onClick={onDismiss}
        >
          <XIcon />
        </Button>
      </div>

      <dl className="mt-3 grid gap-2.5 pl-7 text-foreground">
        {report.warnings.length > 0 && (
          <div>
            <dt className="font-medium text-warning">Needs attention</dt>
            <dd>
              <ul className="mt-1 space-y-1">
                {report.warnings.map((warning) => (
                  <li key={warning} className="flex gap-2">
                    <CircleAlertIcon className="mt-0.5 size-3.5 shrink-0 text-warning" />
                    <span>{warning}</span>
                  </li>
                ))}
              </ul>
            </dd>
          </div>
        )}
        {report.derived.length > 0 && (
          <div>
            <dt className="font-medium">
              Worked out from the text, so check these
            </dt>
            <dd className="text-muted-foreground">
              {report.derived.join(", ")}
            </dd>
          </div>
        )}
        {report.found.length > 0 && (
          <div>
            <dt className="font-medium">Set from the file</dt>
            <dd className="text-muted-foreground">{report.found.join(", ")}</dd>
          </div>
        )}
        {report.skipped.length > 0 && (
          <div>
            <dt className="font-medium">
              Not imported (no field for these yet)
            </dt>
            <dd className="text-muted-foreground">
              {report.skipped.join(", ")}
            </dd>
          </div>
        )}
      </dl>

      <p className="mt-3 pl-7 text-xs text-muted-foreground">
        {meta && meta.status !== "DRAFT"
          ? "Readers still see the live version until you click Update."
          : "Saved to the draft automatically. Check it in Preview before publishing."}
      </p>
    </div>
  );
}

/** Shown on an empty article, where importing is the likeliest next step. */
export function MarkdownImportPrompt({
  onImport,
  busy,
}: {
  onImport: () => void;
  busy: boolean;
}) {
  return (
    <div className="mb-5 flex flex-wrap items-center gap-3 rounded-xl border border-dashed px-4 py-3 text-sm">
      <FileUpIcon className="size-4 shrink-0 text-muted-foreground" />
      <p className="min-w-0 flex-1 text-muted-foreground">
        Written the article elsewhere? Import a Markdown file to fill the title,
        body, FAQs and SEO fields.
      </p>
      <Button
        type="button"
        size="sm"
        variant="outline"
        onClick={onImport}
        disabled={busy}
      >
        {busy ? <Spinner /> : <FileUpIcon />}
        Import .md
      </Button>
    </div>
  );
}
