"use client";

import { useCallback, useEffect, useMemo, useState } from "react";
import { Card, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Input } from "@/components/ui/input";
import { ALL_ORGANIZATIONS } from "@/lib/sag/entities";

/**
 * Obsidian vault importer UI.
 *
 * Calls the server-only `/api/docs/obsidian-import` route, lists every
 * markdown file found in the vault, and lets the user choose which ones to
 * import. On import, raw markdown bodies come back from the POST and we
 * write them into the existing `sag.docs` localStorage store so they show
 * up alongside everything else on /app/docs.
 */

interface PreviewFile {
  relPath: string;
  size: number;
  mtime: string;
  title: string;
  headingCount: number;
  wikiLinkCount: number;
  tags: string[];
  detectedEntitySlug: string | null;
}

interface ImportedDoc {
  id: string;
  title: string;
  body: string;
  relPath: string;
  entitySlug: string | null;
  size: number;
  mtime: string;
}

/**
 * Mirrors the VaultDoc shape from `document-vault.tsx` so imported entries
 * render correctly in the existing docs list without any schema migration.
 */
interface VaultDoc {
  id: string;
  organizationSlug: string;
  docType: string;
  name: string;
  notes?: string;
  externalUrl?: string;
  inlineData?: string;
  inlineMimeType?: string;
  fileSize?: number;
  aiSummary?: string;
  uploadedAt: string;
}

const DOCS_STORAGE_KEY = "sag.docs";
const AUTO_SELECT_BYTES = 100 * 1024;
const DEFAULT_ENTITY_SLUG = "south-armz-global";

function toBase64Utf8(text: string): string {
  if (typeof window === "undefined") return "";
  const bytes = new TextEncoder().encode(text);
  let binary = "";
  for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
  return window.btoa(binary);
}

function readExistingDocs(): VaultDoc[] {
  if (typeof window === "undefined") return [];
  try {
    const raw = window.localStorage.getItem(DOCS_STORAGE_KEY);
    if (!raw) return [];
    const parsed = JSON.parse(raw) as unknown;
    if (Array.isArray(parsed)) return parsed as VaultDoc[];
    return [];
  } catch {
    return [];
  }
}

function writeExistingDocs(docs: VaultDoc[]): void {
  if (typeof window === "undefined") return;
  try {
    window.localStorage.setItem(DOCS_STORAGE_KEY, JSON.stringify(docs));
  } catch {
    // ignore quota errors — caller surfaces a banner
  }
}

export function ObsidianImport() {
  const [files, setFiles] = useState<PreviewFile[]>([]);
  const [vaultRoot, setVaultRoot] = useState<string>("");
  const [selected, setSelected] = useState<Set<string>>(new Set());
  const [loading, setLoading] = useState(true);
  const [importing, setImporting] = useState(false);
  const [search, setSearch] = useState("");
  const [previewError, setPreviewError] = useState<string>("");
  const [importBanner, setImportBanner] = useState<string>("");
  const [importError, setImportError] = useState<string>("");

  const loadPreview = useCallback(async () => {
    setLoading(true);
    setPreviewError("");
    setImportBanner("");
    setImportError("");
    try {
      const resp = await fetch("/api/docs/obsidian-import", { method: "GET" });
      const data = (await resp.json()) as
        | { ok: true; root: string; files: PreviewFile[] }
        | { ok: false; error: string; root?: string };
      if (!resp.ok || data.ok === false) {
        const message =
          data.ok === false ? data.error : `Preview failed (status ${resp.status})`;
        setPreviewError(message);
        setFiles([]);
        setVaultRoot(data.ok === false ? data.root ?? "" : "");
        return;
      }
      setFiles(data.files);
      setVaultRoot(data.root);
      const initial = new Set<string>();
      for (const f of data.files) {
        if (f.size < AUTO_SELECT_BYTES) initial.add(f.relPath);
      }
      setSelected(initial);
    } catch (err) {
      setPreviewError(err instanceof Error ? err.message : "Network error");
      setFiles([]);
    } finally {
      setLoading(false);
    }
  }, []);

  useEffect(() => {
    // eslint-disable-next-line react-hooks/set-state-in-effect -- one-time mount fetch
    void loadPreview();
  }, [loadPreview]);

  const filtered = useMemo(() => {
    if (!search) return files;
    const needle = search.toLowerCase();
    return files.filter(
      (f) =>
        f.title.toLowerCase().includes(needle) ||
        f.relPath.toLowerCase().includes(needle) ||
        f.tags.some((t) => t.toLowerCase().includes(needle))
    );
  }, [files, search]);

  const selectedCount = selected.size;

  function toggleOne(relPath: string) {
    setSelected((prev) => {
      const next = new Set(prev);
      if (next.has(relPath)) next.delete(relPath);
      else next.add(relPath);
      return next;
    });
  }

  function selectAll() {
    setSelected(new Set(filtered.map((f) => f.relPath)));
  }

  function selectNone() {
    setSelected(new Set());
  }

  async function runImport() {
    if (selectedCount === 0) return;
    setImporting(true);
    setImportError("");
    setImportBanner("");
    try {
      const resp = await fetch("/api/docs/obsidian-import", {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({ files: Array.from(selected) }),
      });
      const data = (await resp.json()) as
        | {
            ok: true;
            imported: ImportedDoc[];
            errors?: Array<{ relPath: string; error: string }>;
          }
        | { ok: false; error: string };
      if (!resp.ok || data.ok === false) {
        const message =
          data.ok === false ? data.error : `Import failed (status ${resp.status})`;
        setImportError(message);
        return;
      }
      const existing = readExistingDocs();
      const newDocs: VaultDoc[] = data.imported.map((d) => ({
        id: d.id,
        organizationSlug: d.entitySlug ?? DEFAULT_ENTITY_SLUG,
        docType: "Other",
        name: d.title,
        notes: `Imported from Obsidian: ${d.relPath}`,
        inlineData: toBase64Utf8(d.body),
        inlineMimeType: "text/markdown",
        fileSize: d.size,
        uploadedAt: new Date().toISOString(),
      }));
      writeExistingDocs([...newDocs, ...existing]);
      const skipped = data.errors?.length ?? 0;
      setImportBanner(
        `Imported ${data.imported.length} doc${data.imported.length === 1 ? "" : "s"}` +
          (skipped > 0 ? ` (${skipped} skipped)` : "") +
          ". They appear on /app/docs."
      );
    } catch (err) {
      setImportError(err instanceof Error ? err.message : "Network error");
    } finally {
      setImporting(false);
    }
  }

  return (
    <div className="space-y-6">
      <Card>
        <CardContent className="p-6">
          <div className="flex flex-wrap items-start justify-between gap-3">
            <div className="min-w-0">
              <h2 className="text-base font-semibold">Obsidian vault import</h2>
              <p className="mt-1 text-sm text-muted-foreground max-w-2xl">
                Pulls markdown notes from your local Obsidian vault and drops
                them into the SAG document vault. Files larger than 2 MB are
                skipped; attachments are ignored. The vault is read locally on
                the server, never uploaded.
              </p>
              {vaultRoot && (
                <p className="mt-2 text-[11px] font-mono text-muted-foreground break-all">
                  {vaultRoot}
                </p>
              )}
            </div>
            <div className="flex gap-2 shrink-0">
              <Button
                variant="outline"
                size="sm"
                onClick={loadPreview}
                disabled={loading || importing}
              >
                {loading ? "Loading..." : "Refresh"}
              </Button>
              <Button
                variant="brand"
                size="sm"
                onClick={runImport}
                disabled={importing || loading || selectedCount === 0}
              >
                {importing ? "Importing..." : `Import ${selectedCount} doc${selectedCount === 1 ? "" : "s"}`}
              </Button>
            </div>
          </div>

          {previewError && (
            <div className="mt-4 rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive">
              {previewError}
            </div>
          )}
          {importBanner && (
            <div className="mt-4 rounded-md border border-emerald-500/40 bg-emerald-500/10 p-3 text-sm text-emerald-700 dark:text-emerald-300">
              {importBanner}
            </div>
          )}
          {importError && (
            <div className="mt-4 rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive">
              {importError}
            </div>
          )}
        </CardContent>
      </Card>

      {!previewError && (
        <Card>
          <CardContent className="p-4">
            <div className="flex flex-wrap items-center gap-3">
              <Input
                placeholder="Filter by title, path, or tag..."
                value={search}
                onChange={(e) => setSearch(e.target.value)}
                className="max-w-xs"
                disabled={loading}
              />
              <Button
                variant="ghost"
                size="sm"
                onClick={selectAll}
                disabled={loading || filtered.length === 0}
              >
                Select all
              </Button>
              <Button
                variant="ghost"
                size="sm"
                onClick={selectNone}
                disabled={loading || selectedCount === 0}
              >
                Select none
              </Button>
              <span className="ml-auto text-xs text-muted-foreground tabular-nums">
                {selectedCount} selected · {filtered.length} of {files.length} shown
              </span>
            </div>
          </CardContent>
        </Card>
      )}

      {loading ? (
        <Card>
          <CardContent className="p-8 text-center text-sm text-muted-foreground">
            Walking vault...
          </CardContent>
        </Card>
      ) : !previewError && filtered.length === 0 ? (
        <Card>
          <CardContent className="p-8 text-center text-sm text-muted-foreground">
            {files.length === 0
              ? "No markdown files found in the vault."
              : "No files match the current filter."}
          </CardContent>
        </Card>
      ) : (
        !previewError && (
          <div className="grid gap-2">
            {filtered.map((f) => {
              const checked = selected.has(f.relPath);
              const org = f.detectedEntitySlug
                ? ALL_ORGANIZATIONS.find((o) => o.slug === f.detectedEntitySlug)
                : null;
              return (
                <label
                  key={f.relPath}
                  className="flex items-start gap-3 rounded-md border bg-card p-3 cursor-pointer hover:bg-accent/50"
                >
                  <input
                    type="checkbox"
                    checked={checked}
                    onChange={() => toggleOne(f.relPath)}
                    className="mt-1"
                  />
                  <div className="min-w-0 flex-1">
                    <div className="flex flex-wrap items-center gap-2">
                      <span className="text-sm font-medium truncate">
                        {f.title}
                      </span>
                      {org && (
                        <Badge variant="outline" className="text-[10px]">
                          {org.emoji} {org.name}
                        </Badge>
                      )}
                    </div>
                    <p className="mt-0.5 text-[11px] font-mono text-muted-foreground break-all">
                      {f.relPath}
                    </p>
                    <div className="mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-[11px] text-muted-foreground">
                      <span>{(f.size / 1024).toFixed(1)} KB</span>
                      <span>{f.headingCount} heading{f.headingCount === 1 ? "" : "s"}</span>
                      <span>{f.wikiLinkCount} wiki-link{f.wikiLinkCount === 1 ? "" : "s"}</span>
                      <span>{new Date(f.mtime).toLocaleDateString()}</span>
                      {f.tags.length > 0 && (
                        <span className="flex flex-wrap gap-1">
                          {f.tags.slice(0, 5).map((t) => (
                            <Badge
                              key={t}
                              variant="outline"
                              className="text-[10px] px-1 py-0"
                            >
                              #{t}
                            </Badge>
                          ))}
                          {f.tags.length > 5 && (
                            <span>+{f.tags.length - 5} more</span>
                          )}
                        </span>
                      )}
                    </div>
                  </div>
                </label>
              );
            })}
          </div>
        )
      )}

      <p className="text-xs text-muted-foreground">
        Imports write to the existing <code className="px-1 py-0.5 rounded bg-muted text-[10px]">sag.docs</code>{" "}
        localStorage store. Re-running import creates duplicate entries — clean
        them up from the docs vault.
      </p>
    </div>
  );
}
