/**
 * Ticket detail/edit view for the web workspace (ticket #86) — replaces the
 * list in the column (no floating window), with an explicit back button.
 * Sections are stacked accordions: `Résumé`, `Statut et priorité` and `Carnet`
 * open by default; `Agents assignés`, `Liens` and `Zone dangereuse` collapsed,
 * per carnet #86.
 *
 * Reuses `useTicketDetail` as-is (transport-neutral); only presentation and
 * copy are web-specific, including the conflict message, which this surface
 * renders in French independently of the hook's own (English, desktop-shared)
 * `error` string — the desktop `TicketDetail` does the same thing (its own
 * hardcoded copy keyed off `vm.conflict`, not `vm.error`).
 */

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

import type { Sprint, TicketLinkKind, TicketPriority, TicketStatus } from "@/domain";
import { TICKET_LINK_KINDS, TICKET_PRIORITIES, TICKET_STATUSES, useTicketDetail } from "@/features/tickets";
import { Button, Panel, Spinner, cn } from "@/shared";
import type { WebProjectAgent } from "./useWebProjectAgents";
import { WebConfirmDialog } from "./WebConfirmDialog";
import { WebTicketPickerSheet } from "./WebTicketPickerSheet";
import { WebPriorityBadge, WebStatusBadge, webLinkKindLabel, webPriorityLabel, webStatusLabel } from "./webTicketLabels";

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 WebTicketDetailProps {
  projectId: string;
  ticketRef: string;
  nameOf: (agentId: string) => string;
  assignableAgents: WebProjectAgent[];
  sprints: Sprint[];
  onBack: () => void;
  onOpenRef: (ref: string) => void;
}

function Accordion({
  title,
  defaultOpen,
  children,
}: {
  title: string;
  defaultOpen: boolean;
  children: React.ReactNode;
}) {
  const [open, setOpen] = useState(defaultOpen);
  return (
    <section className="border-b border-border py-2 last:border-b-0">
      <button
        type="button"
        aria-expanded={open}
        onClick={() => setOpen((v) => !v)}
        className="flex min-h-[32px] w-full items-center justify-between text-left text-xs font-semibold uppercase tracking-wide text-muted"
      >
        {title}
        <span aria-hidden="true">{open ? "▾" : "▸"}</span>
      </button>
      {open && <div className="mt-2 flex flex-col gap-2">{children}</div>}
    </section>
  );
}

export function WebTicketDetail({
  projectId,
  ticketRef,
  nameOf,
  assignableAgents,
  sprints,
  onBack,
  onOpenRef,
}: WebTicketDetailProps) {
  const vm = useTicketDetail(projectId, ticketRef);
  const t = vm.ticket;

  const [title, setTitle] = useState("");
  const [description, setDescription] = useState("");
  const [carnet, setCarnet] = useState("");
  const [linkKind, setLinkKind] = useState<TicketLinkKind>("relatesTo");
  const [assignPick, setAssignPick] = useState("");
  const [confirmDelete, setConfirmDelete] = useState(false);
  const [confirmBack, setConfirmBack] = useState(false);
  const [showLinkPicker, setShowLinkPicker] = useState(false);

  useEffect(() => {
    if (vm.deleted) onBack();
  }, [vm.deleted, onBack]);

  // Re-seed the draft only on a genuine (re)load — mirrors the desktop
  // `TicketDetail` (#9): an ordinary optimistic mutation (status/priority)
  // must never clobber an in-progress title/description/carnet edit.
  const reloadCount = vm.reloadCount;
  const seededReload = useRef(-1);
  const baseline = useRef<{ title: string; description: string; carnet: string } | null>(null);
  useEffect(() => {
    if (!t) return;
    if (seededReload.current === reloadCount) return;
    seededReload.current = reloadCount;
    const nextCarnet = t.carnet ?? "";
    const base = baseline.current;
    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 = assignableAgents.filter((a) => !t?.assignedAgentIds.includes(a.id));

  function requestBack() {
    if (dirty) setConfirmBack(true);
    else onBack();
  }

  async function saveAndBack() {
    let ok = true;
    if (dirtyFields) ok = (await vm.updateFields({ title, description })) && ok;
    if (ok && dirtyCarnet) ok = (await vm.saveCarnet(carnet)) && ok;
    setConfirmBack(false);
    if (ok) onBack();
  }

  return (
    <Panel className="flex flex-col" flush>
      <div className="flex items-center justify-between gap-2 border-b border-border px-4 py-3">
        <Button size="sm" variant="ghost" onClick={requestBack}>
          ← Tickets
        </Button>
        <code className="rounded bg-raised px-1.5 py-0.5 font-mono text-xs text-content">{ticketRef}</code>
      </div>

      {vm.conflict && (
        <p role="alert" className="border-b border-warning/40 bg-warning/10 px-4 py-2 text-sm text-warning">
          Ce ticket a été modifié ailleurs et rechargé. Réappliquez votre modification.
        </p>
      )}
      {vm.error && !vm.conflict && (
        <p role="alert" className="border-b border-danger/40 bg-danger/10 px-4 py-2 text-sm text-danger">
          {vm.error}
        </p>
      )}

      {vm.busy && !t ? (
        <div className="flex items-center gap-2 px-4 py-5 text-sm text-muted">
          <Spinner size={14} />
          <span>Chargement du ticket…</span>
        </div>
      ) : !t ? (
        <p className="px-4 py-5 text-sm text-muted">Ticket introuvable.</p>
      ) : (
        <div className="flex flex-col px-4 py-2">
          <div className="flex flex-col gap-1 border-b border-border pb-3">
            <span className="text-sm font-semibold text-content">{t.title}</span>
            <span className="flex flex-wrap items-center gap-1.5 text-xs text-muted">
              <WebStatusBadge status={t.status} />
              <WebPriorityBadge priority={t.priority} />
              {t.sprintId && <span>· {sprints.find((s) => s.id === t.sprintId)?.name ?? "Sprint"}</span>}
            </span>
          </div>

          <Accordion title="Résumé" defaultOpen>
            <label className="flex flex-col gap-1">
              <span className="text-xs font-medium text-muted">Titre</span>
              <input
                aria-label="Titre"
                className={cn(selectClass, "w-full")}
                value={title}
                onChange={(e) => setTitle(e.target.value)}
                disabled={vm.busy}
              />
            </label>
            <label className="flex flex-col gap-1">
              <span className="text-xs font-medium text-muted">Description</span>
              <textarea
                aria-label="Description"
                className={cn(textareaClass, "min-h-24")}
                value={description}
                onChange={(e) => setDescription(e.target.value)}
                disabled={vm.busy}
              />
            </label>
            <div>
              <Button
                size="sm"
                disabled={!dirtyFields || vm.busy}
                loading={vm.busy && dirtyFields}
                onClick={() => void vm.updateFields({ title, description })}
              >
                Enregistrer
              </Button>
            </div>
          </Accordion>

          <Accordion title="Statut et priorité" defaultOpen>
            <label className="flex items-center justify-between gap-2 text-sm">
              <span className="text-muted">Statut</span>
              <select
                aria-label="Statut"
                className={selectClass}
                value={t.status}
                disabled={vm.busy}
                onChange={(e) => void vm.updateFields({ status: e.target.value as TicketStatus })}
              >
                {TICKET_STATUSES.map((s) => (
                  <option key={s} value={s}>
                    {webStatusLabel(s)}
                  </option>
                ))}
              </select>
            </label>
            <label className="flex items-center justify-between gap-2 text-sm">
              <span className="text-muted">Priorité</span>
              <select
                aria-label="Priorité"
                className={selectClass}
                value={t.priority}
                disabled={vm.busy}
                onChange={(e) => void vm.updateFields({ priority: e.target.value as TicketPriority })}
              >
                {TICKET_PRIORITIES.map((p) => (
                  <option key={p} value={p}>
                    {webPriorityLabel(p)}
                  </option>
                ))}
              </select>
            </label>
            {vm.busy && <Spinner size={12} />}
          </Accordion>

          <Accordion
            title="Carnet"
            defaultOpen
          >
            <textarea
              aria-label="Carnet"
              className={cn(textareaClass, "min-h-32 font-mono")}
              value={carnet}
              onChange={(e) => setCarnet(e.target.value)}
              disabled={vm.busy}
            />
            <div>
              <Button
                size="sm"
                disabled={!dirtyCarnet || vm.busy}
                loading={vm.busy && dirtyCarnet}
                onClick={() => void vm.saveCarnet(carnet)}
              >
                Enregistrer le carnet
              </Button>
            </div>
          </Accordion>

          <Accordion title="Agents assignés" defaultOpen={false}>
            {t.assignedAgentIds.length === 0 ? (
              <p className="text-xs text-muted">Aucun agent assigné.</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={`Désassigner ${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="flex items-center gap-2">
              <select
                aria-label="Assigner un agent"
                className={selectClass}
                value={assignPick}
                disabled={vm.busy || assignable.length === 0}
                onChange={(e) => setAssignPick(e.target.value)}
              >
                <option value="">
                  {assignable.length === 0 ? "Aucun autre agent" : "Choisir un 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("");
                }}
              >
                Assigner
              </Button>
            </div>
          </Accordion>

          <Accordion title="Liens" defaultOpen={false}>
            {t.links.length === 0 ? (
              <p className="text-xs text-muted">Aucun ticket lié.</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">{webLinkKindLabel(l.kind)}</span>
                    <button
                      type="button"
                      className="rounded bg-raised px-1.5 py-0.5 font-mono text-xs text-content hover:text-primary"
                      onClick={() => onOpenRef(l.targetRef)}
                    >
                      {l.targetRef}
                    </button>
                    <Button
                      size="sm"
                      variant="ghost"
                      disabled={vm.busy}
                      onClick={() => void vm.unlink(l.targetRef, l.kind)}
                    >
                      Retirer
                    </Button>
                  </li>
                ))}
              </ul>
            )}
            <div className="flex flex-wrap items-center gap-2">
              <select
                aria-label="Type de lien"
                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}>
                    {webLinkKindLabel(k)}
                  </option>
                ))}
              </select>
              <Button size="sm" disabled={vm.busy} onClick={() => setShowLinkPicker(true)}>
                + Lier
              </Button>
            </div>
          </Accordion>

          <Accordion title="Zone dangereuse" defaultOpen={false}>
            <p className="text-xs text-muted">
              Le ticket {ticketRef} sera supprimé définitivement. Cette action ne supprime pas les
              sprints ni les agents assignés.
            </p>
            <div>
              <Button
                size="sm"
                variant="danger"
                disabled={vm.busy}
                onClick={() => setConfirmDelete(true)}
              >
                Supprimer
              </Button>
            </div>
          </Accordion>
        </div>
      )}

      {showLinkPicker && t && (
        <WebTicketPickerSheet
          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);
          }}
          onClose={() => setShowLinkPicker(false)}
        />
      )}

      {confirmDelete && (
        <WebConfirmDialog
          title="Supprimer ce ticket ?"
          body={`Le ticket ${ticketRef} sera supprimé définitivement. Cette action ne supprime pas les sprints ni les agents assignés.`}
          confirmLabel="Supprimer"
          busy={vm.busy}
          onCancel={() => setConfirmDelete(false)}
          onConfirm={async () => {
            const ok = await vm.remove();
            if (ok) setConfirmDelete(false);
          }}
        />
      )}

      {confirmBack && (
        <div
          role="alertdialog"
          aria-modal="true"
          aria-label="Modifications non enregistrées"
          className="fixed inset-0 flex items-center justify-center bg-black/50 p-4"
        >
          <div className="flex w-full max-w-sm flex-col gap-3 rounded-lg border border-border bg-raised p-4 shadow-xl">
            <h3 className="text-sm font-semibold text-content">
              Des modifications ne sont pas enregistrées.
            </h3>
            <div className="flex flex-wrap justify-end gap-2">
              <Button size="sm" variant="ghost" disabled={vm.busy} onClick={() => setConfirmBack(false)}>
                Continuer l'édition
              </Button>
              <Button size="sm" variant="ghost" disabled={vm.busy} onClick={onBack}>
                Ignorer
              </Button>
              <Button size="sm" loading={vm.busy} disabled={vm.busy} onClick={() => void saveAndBack()}>
                Enregistrer et quitter
              </Button>
            </div>
          </div>
        </div>
      )}
    </Panel>
  );
}
