/**
 * Ticket detail / edit overlay (F3, F4, F5, F7).
 *
 * A full-screen dialog mirroring the ticket: editable title/description, status
 * and priority, the ticket-scoped Markdown **carnet** (replaceable save, F4),
 * links to other tickets (F5) and agent assignments limited to known agents
 * (F5). Optimistic-concurrency conflicts surface a clear banner and reload
 * (F3). `#ref`s are clickable throughout (F7), and a one-click "delegation
 * context" prefill lets the user hand an agent "work on #N" (F7).
 */

import { useEffect, useRef, useState } from "react";

import type { TicketLinkKind } from "@/domain";
import { Button, FloatingWindow, Input, Spinner, cn } from "@/shared";
import { useTicketDetail } from "./useTicketDetail";
import { useProjectAgents } from "./useProjectAgents";
import { TicketAssistantPanel } from "./TicketAssistantPanel";
import { TicketPicker } from "./TicketPicker";
import {
  PriorityBadge,
  StatusBadge,
  TICKET_LINK_KINDS,
  TICKET_PRIORITIES,
  TICKET_STATUSES,
  TicketRef,
  linkKindLabel,
  linkifyTicketRefs,
  priorityLabel,
  statusLabel,
} from "./ticketMeta";

const selectClass = cn(
  "h-9 rounded-md bg-raised px-3 text-sm text-content",
  "border border-border outline-none transition-colors",
  "focus:border-primary disabled:cursor-not-allowed disabled:opacity-50",
);

const textareaClass = cn(
  "w-full rounded-md bg-raised p-3 text-sm text-content",
  "border border-border outline-none transition-colors",
  "focus:border-primary disabled:cursor-not-allowed disabled:opacity-50",
);

export interface TicketDetailProps {
  projectId: string;
  ticketRef: string;
  onClose: () => void;
  /** Navigate to another ticket (from a link or an inline `#ref`, F7). */
  onOpenRef: (ref: string) => void;
}

async function copyToClipboard(text: string): Promise<boolean> {
  if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
    await navigator.clipboard.writeText(text);
    return true;
  }
  return false;
}

function Section({
  title,
  hint,
  children,
}: {
  title: string;
  hint?: string;
  children: React.ReactNode;
}) {
  return (
    <section className="flex flex-col gap-2 border-b border-border px-5 py-4">
      <div>
        <h4 className="text-xs font-semibold uppercase tracking-wide text-muted">
          {title}
        </h4>
        {hint && <p className="mt-0.5 text-xs text-muted">{hint}</p>}
      </div>
      {children}
    </section>
  );
}

export function TicketDetail({
  projectId,
  ticketRef,
  onClose,
  onOpenRef,
}: TicketDetailProps) {
  const vm = useTicketDetail(projectId, ticketRef);
  const { agents, nameOf } = useProjectAgents(projectId);
  const t = vm.ticket;

  // Local draft state. Re-seeded only on a genuine (re)load of the ticket —
  // initial load, an external-event refresh, or a conflict reload — tracked via
  // the hook's `reloadCount`. It is deliberately NOT re-seeded on an ordinary
  // optimistic mutation (e.g. changing the priority), which bumps the ticket's
  // version but must not clobber an in-progress, unsaved title/description edit
  // (#9).
  const [title, setTitle] = useState("");
  const [description, setDescription] = useState("");
  const [carnet, setCarnet] = useState("");
  const [linkKind, setLinkKind] = useState<TicketLinkKind>("relatesTo");
  const [assignPick, setAssignPick] = useState("");
  const [copied, setCopied] = useState(false);
  // Confirmation shown when the user tries to close with unsaved changes.
  const [confirmClose, setConfirmClose] = useState(false);
  // Confirmation shown before deleting the ticket (#6).
  const [confirmDelete, setConfirmDelete] = useState(false);
  // Add-link TicketPicker open state (#17): replaces the manual `#id` input.
  const [showLinkPicker, setShowLinkPicker] = useState(false);
  // AI writing-assistant window open state (#17): opened from the bottom-right
  // floating button so the assistant is an obvious, first-class affordance.
  const [showAssistant, setShowAssistant] = useState(false);

  // The ticket was deleted (by this view or another actor): close the surface.
  // The removal from lists flows from the same `issueDeleted` event (#6).
  useEffect(() => {
    if (vm.deleted) onClose();
  }, [vm.deleted, onClose]);

  const reloadCount = vm.reloadCount;
  const seededReload = useRef(-1);
  // The persisted values the draft was last seeded from. Lets a benign reload
  // (echo of one's own priority change, or an external edit to an untouched
  // field) adopt fresh values field-by-field WITHOUT clobbering a field the user
  // is currently editing (#9).
  const baseline = useRef<{
    title: string;
    description: string;
    carnet: string;
  } | null>(null);
  useEffect(() => {
    if (!t) return;
    // Re-seed once per (re)load. An optimistic mutation (e.g. a priority change)
    // changes `t` without bumping `reloadCount`, so this guard short-circuits and
    // preserves the draft (#9).
    if (seededReload.current === reloadCount) return;
    seededReload.current = reloadCount;
    const nextCarnet = t.carnet ?? "";
    const base = baseline.current;
    // Force a full reset on the initial load and on a conflict reload (the local
    // edit is stale — the user must re-apply). A benign reload preserves any
    // field the user has edited since the last seed.
    const forced = base === null || vm.conflict;
    if (forced) {
      setTitle(t.title);
      setDescription(t.description);
      setCarnet(nextCarnet);
    } else {
      setTitle((cur) => (cur === base.title ? t.title : cur));
      setDescription((cur) => (cur === base.description ? t.description : cur));
      setCarnet((cur) => (cur === base.carnet ? nextCarnet : cur));
    }
    baseline.current = {
      title: t.title,
      description: t.description,
      carnet: nextCarnet,
    };
  }, [t, reloadCount, vm.conflict]);

  const dirtyFields =
    !!t && (title !== t.title || description !== t.description);
  const dirtyCarnet = !!t && carnet !== (t.carnet ?? "");
  const dirty = dirtyFields || dirtyCarnet;
  const assignable = agents.filter((a) => !t?.assignedAgentIds.includes(a.id));

  /** Close guard: prompt before discarding unsaved edits, else close directly. */
  function requestClose() {
    if (dirty) setConfirmClose(true);
    else onClose();
  }

  /** Save whatever is dirty, then close only if every save succeeded. */
  async function saveAndClose() {
    let ok = true;
    if (dirtyFields) ok = (await vm.updateFields({ title, description })) && ok;
    if (ok && dirtyCarnet) ok = (await vm.saveCarnet(carnet)) && ok;
    setConfirmClose(false);
    // On a failed save (e.g. a version conflict) stay open so the banner shows.
    if (ok) onClose();
  }

  async function copyDelegation() {
    if (!t) return;
    const ok = await copyToClipboard(
      `Travaille sur le ticket ${t.ref} : ${t.title}`,
    );
    if (ok) {
      setCopied(true);
      window.setTimeout(() => setCopied(false), 1200);
    }
  }

  return (
    <FloatingWindow
      open
      title={`ticket ${ticketRef}`}
      size="xl"
      onClose={requestClose}
    >
      {/* ── Toolbar (badges + ticket-level actions) ── */}
      <div className="flex flex-wrap items-center justify-between gap-2 border-b border-border pb-3">
        <div className="flex min-w-0 items-center gap-2">
          <TicketRef ticketRef={ticketRef} />
          {t && <StatusBadge status={t.status} />}
          {t && <PriorityBadge priority={t.priority} />}
          <span className="truncate text-sm font-medium text-content">
            {t?.title ?? ticketRef}
          </span>
        </div>
        <div className="flex shrink-0 items-center gap-2">
          <Button
            size="sm"
            variant="ghost"
            disabled={!t}
            onClick={() => void copyDelegation()}
            title="Copy a delegation prompt to hand this ticket to an agent"
          >
            {copied ? "Copied" : "Delegation context"}
          </Button>
          <Button
            size="sm"
            variant="ghost"
            disabled={!t || vm.busy}
            onClick={() => setConfirmDelete(true)}
            className="text-danger hover:text-danger"
            title="Supprimer définitivement ce ticket"
          >
            Supprimer
          </Button>
        </div>
      </div>

      {vm.conflict && (
        <p
          role="alert"
          className="border-b border-warning/40 bg-warning/10 px-1 py-2 text-sm text-warning"
        >
          This ticket was modified elsewhere and reloaded. Re-apply your change.
        </p>
      )}
      {vm.error && !vm.conflict && (
        <p
          role="alert"
          className="border-b border-danger/40 bg-danger/10 px-1 py-2 text-sm text-danger"
        >
          {vm.error}
        </p>
      )}

      {vm.busy && !t ? (
        <div className="flex items-center gap-2 py-5 text-sm text-muted">
          <Spinner size={14} />
          <span>Loading ticket…</span>
        </div>
      ) : !t ? (
        <div className="flex items-center justify-center py-10 text-sm text-muted">
          Ticket not found.
        </div>
      ) : (
        <div className="-mx-4">
          {/* ── Fields (F3) ── */}
          <Section title="Details">
            <label className="text-xs font-medium text-muted" htmlFor="td-title">
              Title
            </label>
            <Input
              id="td-title"
              value={title}
              onChange={(e) => setTitle(e.target.value)}
              disabled={vm.busy}
            />
            <label
              className="mt-2 text-xs font-medium text-muted"
              htmlFor="td-desc"
            >
              Description
            </label>
            <textarea
              id="td-desc"
              className={cn(textareaClass, "min-h-24")}
              value={description}
              onChange={(e) => setDescription(e.target.value)}
              disabled={vm.busy}
            />
            {description.trim() && (
              <p className="whitespace-pre-wrap break-words rounded-md bg-raised/40 px-3 py-2 text-xs text-muted">
                {linkifyTicketRefs(description, onOpenRef)}
              </p>
            )}
            <div className="mt-1 flex items-center gap-2">
              <Button
                size="sm"
                disabled={!dirtyFields || vm.busy}
                loading={vm.busy && dirtyFields}
                onClick={() =>
                  void vm.updateFields({ title, description })
                }
              >
                Save changes
              </Button>
              {dirtyFields && (
                <Button
                  size="sm"
                  variant="ghost"
                  disabled={vm.busy}
                  onClick={() => {
                    setTitle(t.title);
                    setDescription(t.description);
                  }}
                >
                  Reset
                </Button>
              )}
            </div>
          </Section>

          {/* ── Status & priority (F3) ── */}
          <Section title="Status & priority">
            <div className="flex flex-wrap items-center gap-3">
              <label className="flex items-center gap-2 text-xs text-muted">
                Status
                <select
                  aria-label="ticket status"
                  className={selectClass}
                  value={t.status}
                  disabled={vm.busy}
                  onChange={(e) =>
                    void vm.updateFields({
                      status: e.target.value as (typeof TICKET_STATUSES)[number],
                    })
                  }
                >
                  {TICKET_STATUSES.map((s) => (
                    <option key={s} value={s}>
                      {statusLabel(s)}
                    </option>
                  ))}
                </select>
              </label>
              <label className="flex items-center gap-2 text-xs text-muted">
                Priority
                <select
                  aria-label="ticket priority"
                  className={selectClass}
                  value={t.priority}
                  disabled={vm.busy}
                  onChange={(e) =>
                    void vm.updateFields({
                      priority:
                        e.target.value as (typeof TICKET_PRIORITIES)[number],
                    })
                  }
                >
                  {TICKET_PRIORITIES.map((p) => (
                    <option key={p} value={p}>
                      {priorityLabel(p)}
                    </option>
                  ))}
                </select>
              </label>
            </div>
          </Section>

          {/* ── Carnet (F4) ── */}
          <Section
            title="Carnet"
            hint="Markdown notebook scoped to this ticket only — separate from project memory. Saving replaces the whole carnet."
          >
            <textarea
              aria-label="ticket carnet"
              className={cn(textareaClass, "min-h-40 font-mono")}
              value={carnet}
              onChange={(e) => setCarnet(e.target.value)}
              disabled={vm.busy}
            />
            <div className="flex items-center gap-2">
              <Button
                size="sm"
                disabled={!dirtyCarnet || vm.busy}
                loading={vm.busy && dirtyCarnet}
                onClick={() => void vm.saveCarnet(carnet)}
              >
                Save carnet
              </Button>
              {dirtyCarnet && (
                <Button
                  size="sm"
                  variant="ghost"
                  disabled={vm.busy}
                  onClick={() => setCarnet(t.carnet ?? "")}
                >
                  Reset
                </Button>
              )}
            </div>
          </Section>

          {/* ── Links (F5) ── */}
          <Section title="Linked tickets">
            {t.links.length === 0 ? (
              <p className="text-xs text-muted">No linked tickets.</p>
            ) : (
              <ul className="flex flex-col gap-1">
                {t.links.map((l) => (
                  <li
                    key={`${l.kind}-${l.targetRef}`}
                    className="flex items-center gap-2 text-sm"
                  >
                    <span className="text-xs text-muted">
                      {linkKindLabel(l.kind)}
                    </span>
                    <TicketRef ticketRef={l.targetRef} onOpen={onOpenRef} />
                    <Button
                      size="sm"
                      variant="ghost"
                      disabled={vm.busy}
                      onClick={() => void vm.unlink(l.targetRef, l.kind)}
                    >
                      Remove
                    </Button>
                  </li>
                ))}
              </ul>
            )}
            {/* Add a link via the TicketPicker popup (#17): the kind is chosen
                here, the target ticket is selected in the popup (no manual #id). */}
            <div className="mt-1 flex flex-wrap items-center gap-2">
              <select
                aria-label="link kind"
                className={selectClass}
                value={linkKind}
                disabled={vm.busy}
                onChange={(e) => setLinkKind(e.target.value as TicketLinkKind)}
              >
                {TICKET_LINK_KINDS.map((k) => (
                  <option key={k} value={k}>
                    {linkKindLabel(k)}
                  </option>
                ))}
              </select>
              <Button
                size="sm"
                aria-label="add link"
                disabled={vm.busy}
                onClick={() => setShowLinkPicker(true)}
              >
                Add link
              </Button>
            </div>
          </Section>

          {/* ── Assignments (F5) ── */}
          <Section
            title="Assigned agents"
            hint="Only agents known to this project can be assigned."
          >
            {t.assignedAgentIds.length === 0 ? (
              <p className="text-xs text-muted">No agents assigned.</p>
            ) : (
              <ul className="flex flex-wrap gap-2">
                {t.assignedAgentIds.map((id) => (
                  <li
                    key={id}
                    className="flex items-center gap-1 rounded-full bg-raised px-2 py-0.5 text-xs text-content"
                  >
                    {nameOf(id)}
                    <button
                      type="button"
                      aria-label={`unassign ${nameOf(id)}`}
                      className="text-muted hover:text-danger disabled:opacity-50"
                      disabled={vm.busy}
                      onClick={() => void vm.assign(id, false)}
                    >
                      ×
                    </button>
                  </li>
                ))}
              </ul>
            )}
            <div className="mt-1 flex items-center gap-2">
              <select
                aria-label="assign agent"
                className={selectClass}
                value={assignPick}
                disabled={vm.busy || assignable.length === 0}
                onChange={(e) => setAssignPick(e.target.value)}
              >
                <option value="">
                  {assignable.length === 0
                    ? "No more agents"
                    : "Select an agent…"}
                </option>
                {assignable.map((a) => (
                  <option key={a.id} value={a.id}>
                    {a.name}
                  </option>
                ))}
              </select>
              <Button
                size="sm"
                disabled={!assignPick || vm.busy}
                onClick={async () => {
                  const ok = await vm.assign(assignPick, true);
                  if (ok) setAssignPick("");
                }}
              >
                Assign
              </Button>
            </div>
          </Section>

          {/* ── Add-link picker (#17) — nested (token 60) over this window ── */}
          <TicketPicker
            open={showLinkPicker}
            projectId={projectId}
            title="Lier un ticket"
            selectionMode="single"
            excludeRefs={[ticketRef, ...t.links.map((l) => l.targetRef)]}
            onSelect={(result) => {
              const picked = Array.isArray(result) ? result[0] : result;
              if (picked) void vm.link(picked.ref, linkKind);
              setShowLinkPicker(false);
            }}
            onClose={() => setShowLinkPicker(false)}
          />
        </div>
      )}

      {/* ── Floating "Assistant IA" button (#17), bottom-right of the window.
          Made prominent per the explicit user request; opens the ticket-writing
          AI assistant in a nested window. Sticks to the window's bottom edge. ── */}
      {t && (
        <div className="pointer-events-none sticky bottom-0 z-10 -mx-4 -mb-4 flex justify-end px-4 pb-4 pt-2">
          <Button
            aria-label="ouvrir l'assistant IA"
            className="pointer-events-auto shadow-lg"
            onClick={() => setShowAssistant(true)}
          >
            🤖 Assistant IA
          </Button>
        </div>
      )}

      {/* AI writing assistant, in a nested window (token 60) above this one. */}
      <FloatingWindow
        open={showAssistant}
        title={`Assistant IA — ${ticketRef}`}
        size="lg"
        onClose={() => setShowAssistant(false)}
      >
        <TicketAssistantPanel projectId={projectId} ticketRef={ticketRef} />
      </FloatingWindow>

      {/* ── Unsaved-changes confirmation on close (#9) ── */}
      {confirmClose && (
        <div
          role="dialog"
          aria-modal="true"
          aria-label="unsaved changes"
          className="fixed inset-0 z-[60] flex items-center justify-center bg-black/55 p-4"
        >
          <div className="w-full max-w-sm rounded-lg border border-border bg-surface p-5 shadow-xl">
            <h4 className="text-sm font-semibold text-content">
              Modifications non sauvegardées
            </h4>
            <p className="mt-2 text-sm text-muted">
              Vous avez des modifications non sauvegardées. Voulez-vous les
              sauvegarder avant de fermer&nbsp;?
            </p>
            <div className="mt-4 flex flex-wrap items-center justify-end gap-2">
              <Button
                size="sm"
                variant="ghost"
                aria-label="cancel close"
                disabled={vm.busy}
                onClick={() => setConfirmClose(false)}
              >
                Annuler
              </Button>
              <Button
                size="sm"
                variant="ghost"
                aria-label="close without saving"
                disabled={vm.busy}
                onClick={onClose}
                className="text-danger hover:text-danger"
              >
                Fermer sans sauvegarder
              </Button>
              <Button
                size="sm"
                aria-label="save and close"
                loading={vm.busy}
                disabled={vm.busy}
                onClick={() => void saveAndClose()}
              >
                Sauvegarder
              </Button>
            </div>
          </div>
        </div>
      )}

      {/* ── Delete confirmation (#6) ── */}
      {confirmDelete && (
        <div
          role="dialog"
          aria-modal="true"
          aria-label="confirmer la suppression"
          className="fixed inset-0 z-[60] flex items-center justify-center bg-black/55 p-4"
        >
          <div className="w-full max-w-sm rounded-lg border border-border bg-surface p-5 shadow-xl">
            <h4 className="text-sm font-semibold text-content">
              Supprimer le ticket
            </h4>
            <p className="mt-2 text-sm text-muted">
              Supprimer définitivement le ticket&nbsp;{ticketRef}&nbsp;? Cette
              action est irréversible.
            </p>
            <div className="mt-4 flex flex-wrap items-center justify-end gap-2">
              <Button
                size="sm"
                variant="ghost"
                aria-label="cancel delete"
                disabled={vm.busy}
                onClick={() => setConfirmDelete(false)}
              >
                Annuler
              </Button>
              <Button
                size="sm"
                variant="danger"
                aria-label="confirm delete"
                loading={vm.busy}
                disabled={vm.busy}
                onClick={async () => {
                  const ok = await vm.remove();
                  // On success the surface closes via the `issueDeleted` event
                  // (the `deleted` flag → onClose effect); just drop the dialog.
                  if (ok) setConfirmDelete(false);
                }}
              >
                Supprimer
              </Button>
            </div>
          </div>
        </div>
      )}
    </FloatingWindow>
  );
}
