import { useEffect, useRef, useState } from "react";
import { Plus, Pencil, X, HelpCircle, ChevronDown, ChevronRight, ArrowUp, ArrowDown } from "lucide-react";
import { api } from "../api";
import { useApp } from "../app-state";
import type { CommitInfo } from "../types";
import { Icon } from "./ui/Icon";
import { Button } from "./ui/Button";

const statusIcon: Record<string, React.ReactNode> = {
  A: <Icon icon={Plus} className="size-3 text-teal-600" />,
  M: <Icon icon={Pencil} className="size-3 text-amber-600" />,
  D: <Icon icon={X} className="size-3 text-rose-500" />,
  "?": <Icon icon={HelpCircle} className="size-3 text-ink-subtle" />,
};

export function GitPanel() {
  const { state, dispatch } = useApp();
  const [commitMsg, setCommitMsg] = useState("");
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const inputRef = useRef<HTMLInputElement | null>(null);
  const [pushBusy, setPushBusy] = useState(false);
  const [pullBusy, setPullBusy] = useState(false);
  const [remoteUrl, setRemoteUrl] = useState("");
  const [showRemoteInput, setShowRemoteInput] = useState(false);
  const [log, setLog] = useState<CommitInfo[]>([]);
  const [logOpen, setLogOpen] = useState(false);

  useEffect(() => {
    if (state.commitFocus > 0) {
      inputRef.current?.focus();
    }
  }, [state.commitFocus]);

  useEffect(() => {
    if (!state.currentProject) {
      setLog([]);
      return;
    }
    let cancelled = false;
    const project = state.currentProject;
    setLog([]);
    api
      .gitLog(project, 10)
      .then((r) => { if (!cancelled) setLog(r.commits); })
      .catch(() => {});
    return () => { cancelled = true; };
  }, [state.currentProject, state.gitVersion]);

  const s = state.gitStatus;
  if (!state.currentProject) return null;

  const hasChanges = s && !s.clean;
  const canCommit = commitMsg.trim() && hasChanges && !busy;

  async function handleCommit() {
    if (!canCommit || !state.currentProject) return;
    setBusy(true);
    setError(null);
    try {
      let identity: { name?: string; email?: string } | undefined;
      try {
        const raw = localStorage.getItem("typst-leaf.git-identity");
        const parsed = raw ? JSON.parse(raw) : undefined;
        if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
          identity = {
            name: typeof parsed.name === "string" ? parsed.name : undefined,
            email: typeof parsed.email === "string" ? parsed.email : undefined,
          };
        }
      } catch {
        identity = undefined;
      }
      const res = await api.gitCommit(state.currentProject, commitMsg.trim(), identity);
      if (!res.ok) {
        setError(res.error ?? "commit failed");
      } else {
        setCommitMsg("");
        dispatch({ type: "bumpGitVersion" });
        dispatch({ type: "pushNotice", kind: "success", message: "Committed" });
      }
    } catch (e) {
      setError(String(e));
    } finally {
      setBusy(false);
    }
  }

  async function handlePush() {
    if (!state.currentProject || pushBusy) return;
    setPushBusy(true);
    setError(null);
    try {
      if (!s?.hasRemote) {
        setShowRemoteInput(true);
        setPushBusy(false);
        return;
      }
      const res = await api.gitPush(state.currentProject);
      if (!res.ok) setError(res.error ?? "push failed");
      else {
        dispatch({ type: "bumpGitVersion" });
        dispatch({ type: "pushNotice", kind: "success", message: "Pushed" });
      }
    } catch (e) {
      setError(String(e));
    } finally {
      setPushBusy(false);
    }
  }

  async function handlePull() {
    if (!state.currentProject || pullBusy) return;
    setPullBusy(true);
    setError(null);
    try {
      if (!s?.hasRemote) {
        setShowRemoteInput(true);
        setPullBusy(false);
        return;
      }
      const res = await api.gitPull(state.currentProject);
      if (!res.ok) setError(res.error ?? "pull failed");
      else {
        dispatch({ type: "bumpGitVersion" });
        dispatch({ type: "pushNotice", kind: "success", message: "Pulled latest" });
      }
    } catch (e) {
      setError(String(e));
    } finally {
      setPullBusy(false);
    }
  }

  async function handleSetRemote() {
    if (!remoteUrl.trim() || !state.currentProject) return;
    try {
      await api.setRemote(state.currentProject, remoteUrl.trim());
      setShowRemoteInput(false);
      setRemoteUrl("");
      dispatch({ type: "bumpGitVersion" });
    } catch (e) {
      setError(String(e));
    }
  }

  function renderFileRow(
    f: { path: string; status: string },
    group: "staged" | "unstaged" | "untracked",
  ) {
    const groupClasses: Record<string, string> = {
      staged: "bg-teal-50 hover:bg-teal-100",
      unstaged: "hover:bg-surface-hover",
      untracked: "hover:bg-surface-hover",
    };
    return (
      <div
        key={f.path}
        className={`flex items-center gap-1.5 text-xs font-mono cursor-pointer px-1 py-0.5 rounded transition-colors ${groupClasses[group]}`}
        onClick={() => dispatch({ type: "openFile", path: f.path })}
      >
        <span className="w-3.5 flex items-center justify-center">
          {statusIcon[f.status] ?? <Icon icon={HelpCircle} className="size-3 text-ink-subtle" />}
        </span>
        <span className="truncate text-ink-muted">{f.path}</span>
      </div>
    );
  }

  return (
    <div className="border-t border-line">
      <div className="px-3 py-1.5 text-xs uppercase tracking-wide text-ink-subtle flex items-center gap-1">
        <span>Git</span>
        {s && (
          <span className="ml-auto text-ink-muted font-mono normal-case flex items-center gap-1">
            <span>{s.branch}</span>
            {s.ahead > 0 && (
              <span className="text-teal-600 flex items-center gap-0.5">
                <Icon icon={ArrowUp} className="size-2.5" />{s.ahead}
              </span>
            )}
            {s.behind > 0 && (
              <span className="text-amber-600 flex items-center gap-0.5">
                <Icon icon={ArrowDown} className="size-2.5" />{s.behind}
              </span>
            )}
          </span>
        )}
      </div>

      <div className="px-3 pb-1.5 text-xs text-ink-muted">
        {s && !s.clean && (
          <span className="text-amber-600">
            {s.unstaged.length + s.staged.length} uncommitted
          </span>
        )}
        {s && s.clean && <span className="text-teal-600">clean</span>}
      </div>

      {/* Changed files — dedupe by path; unstaged wins */}
      {s && !s.clean && (() => {
        const byPath = new Map<string, { path: string; status: string; group: "staged" | "unstaged" }>();
        for (const f of s.unstaged) byPath.set(f.path, { ...f, group: "unstaged" });
        for (const f of s.staged) if (!byPath.has(f.path)) byPath.set(f.path, { ...f, group: "staged" });
        const rows = Array.from(byPath.values());
        return (
          <div className="px-3 pb-1">
            <div className="text-[10px] uppercase tracking-wider text-ink-subtle mb-0.5">
              Changed files
            </div>
            {rows.map((f) => renderFileRow(f, f.group))}
          </div>
        );
      })()}

      {/* Commit compose */}
      <div className="px-3 pb-1.5 flex flex-col gap-1.5">
        <div className="flex gap-1">
          <input
            ref={inputRef}
            type="text"
            value={commitMsg}
            onChange={(e) => setCommitMsg(e.target.value)}
            onKeyDown={(e) => {
              if (e.key === "Enter") void handleCommit();
            }}
            placeholder={hasChanges ? "Commit message" : "No changes to commit"}
            disabled={!hasChanges}
            className="flex-1 min-w-0 px-2 py-1 text-sm bg-surface text-ink border border-line-strong rounded-md placeholder:text-ink-subtle outline-none focus:border-teal-500 focus:ring-1 focus:ring-teal-100 transition-colors duration-150 disabled:opacity-40 disabled:cursor-not-allowed"
          />
          <Button size="sm" onClick={() => void handleCommit()} disabled={!canCommit}>
            {busy ? "..." : "Commit"}
          </Button>
        </div>

        <div className="flex gap-1">
          <Button
            variant="secondary"
            size="sm"
            className="flex-1"
            onClick={() => void handlePush()}
            disabled={pushBusy}
          >
            <Icon icon={ArrowUp} className="size-3" />
            {pushBusy ? "..." : "Push"}
          </Button>
          <Button
            variant="secondary"
            size="sm"
            className="flex-1"
            onClick={() => void handlePull()}
            disabled={pullBusy}
          >
            <Icon icon={ArrowDown} className="size-3" />
            {pullBusy ? "..." : "Pull"}
          </Button>
        </div>

        {showRemoteInput && (
          <div className="flex gap-1">
            <input
              type="text"
              value={remoteUrl}
              onChange={(e) => setRemoteUrl(e.target.value)}
              placeholder="git remote URL"
              className="flex-1 min-w-0 px-2 py-1 text-sm bg-surface text-ink border border-line-strong rounded-md placeholder:text-ink-subtle outline-none focus:border-teal-500 focus:ring-1 focus:ring-teal-100"
            />
            <Button size="sm" onClick={() => void handleSetRemote()} disabled={!remoteUrl.trim()}>
              Set
            </Button>
          </div>
        )}

        {error && (
          <div className="text-xs text-rose-600 break-words">{error}</div>
        )}
      </div>

      {/* Commit log */}
      {log.length > 0 && (
        <div className="border-t border-line">
          <button
            type="button"
            onClick={() => setLogOpen(!logOpen)}
            className="w-full flex items-center gap-1 px-3 py-1 text-xs text-ink-subtle hover:text-ink uppercase tracking-wide transition-colors"
          >
            <span>Log</span>
            <span className="ml-auto">
              <Icon icon={logOpen ? ChevronDown : ChevronRight} className="size-3" />
            </span>
          </button>
          {logOpen && (
            <div className="max-h-32 overflow-y-auto px-3 pb-1.5 flex flex-col gap-1">
              {log.map((c) => (
                <div
                  key={c.sha}
                  className="text-xs text-ink-muted leading-tight"
                  title={`${c.sha} — ${c.author} on ${c.date}`}
                >
                  <span className="font-mono text-ink-subtle">{c.sha.slice(0, 7)}</span>
                  {" "}
                  <span className="text-ink">{c.message}</span>
                </div>
              ))}
            </div>
          )}
        </div>
      )}
    </div>
  );
}
