/**
 * "Tickets" tab of the web workspace (ticket #86).
 *
 * A mobile-first adaptation of the desktop `TicketsPanel`/`TicketDetail` pair:
 * one vertical column that swaps between **list**, **create**, and **detail**
 * modes in place — never a floating window / dock, per carnet #86. Reuses the
 * transport-neutral `useTickets` view-model as-is; only the presentation (and
 * the French labels — decision #78) is web-specific.
 */

import { useState } from "react";

import type { Sprint, TicketSummary } from "@/domain";
import type { TicketListSortField } from "@/ports";
import { TICKET_PRIORITIES, TICKET_STATUSES, useTickets } from "@/features/tickets";
import { Button, Input, Panel, Spinner, cn } from "@/shared";
import { WebTicketCreate } from "./WebTicketCreate";
import { WebTicketDetail } from "./WebTicketDetail";
import { useWebProjectAgents } from "./useWebProjectAgents";
import { WebStatusBadge, WebPriorityBadge, webStatusLabel, webPriorityLabel } from "./webTicketLabels";

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

const SORT_FIELD_LABEL: Record<TicketListSortField, string> = {
  number: "Numéro",
  priority: "Priorité",
  status: "Statut",
  title: "Titre",
};

type Mode = { type: "list" } | { type: "create" } | { type: "detail"; ref: string };

export interface WebTicketsViewProps {
  projectId: string;
  /**
   * When set, the list initially shows only this sprint's tickets (from the
   * Sprints tab's "Voir tickets"), with a link back to the full list.
   */
  focusSprintId?: string | null;
}

export function WebTicketsView({ projectId, focusSprintId = null }: WebTicketsViewProps) {
  const vm = useTickets(projectId);
  const webAgents = useWebProjectAgents(projectId);
  const [mode, setMode] = useState<Mode>({ type: "list" });
  const [sprintFocus, setSprintFocus] = useState<string | null>(focusSprintId);

  if (mode.type === "detail") {
    return (
      <WebTicketDetail
        key={mode.ref}
        projectId={projectId}
        ticketRef={mode.ref}
        nameOf={webAgents.nameOf}
        assignableAgents={webAgents.agents}
        sprints={vm.sprints}
        onBack={() => setMode({ type: "list" })}
        onOpenRef={(ref) => setMode({ type: "detail", ref })}
      />
    );
  }

  if (mode.type === "create") {
    return (
      <WebTicketCreate
        sprints={vm.sprints}
        busy={vm.busy}
        error={vm.error}
        onCancel={() => setMode({ type: "list" })}
        onCreate={async (input, sprintId) => {
          const created = await vm.create(input);
          if (!created) return;
          if (sprintId) await vm.assignSprint(created.ref, sprintId);
          setMode({ type: "detail", ref: created.ref });
        }}
      />
    );
  }

  const items = vm.list?.items ?? [];
  const hasFilters =
    !!vm.query.text ||
    (vm.query.statuses?.length ?? 0) > 0 ||
    (vm.query.priorities?.length ?? 0) > 0 ||
    !!vm.query.assignedAgentId ||
    !!sprintFocus;

  const sprintIds = new Set(vm.sprints.map((s) => s.id));
  const visibleSprints = sprintFocus ? vm.sprints.filter((s) => s.id === sprintFocus) : vm.sprints;
  const grouped = visibleSprints
    .map((sprint) => ({ sprint, tickets: items.filter((t) => t.sprintId === sprint.id) }))
    .filter((g) => g.tickets.length > 0);
  const noSprint = sprintFocus
    ? []
    : items.filter((t) => !t.sprintId || !sprintIds.has(t.sprintId));

  function resetFilters() {
    vm.setQuery({});
    setSprintFocus(null);
  }

  return (
    <Panel
      className="flex flex-col"
      title="Tickets"
      actions={
        <Button size="sm" onClick={() => setMode({ type: "create" })}>
          + Ticket
        </Button>
      }
    >
      {vm.error && (
        <div className="mb-3 flex flex-wrap items-center justify-between gap-2 rounded-md border border-danger/40 bg-danger/10 px-3 py-2 text-sm text-danger">
          <span>{vm.error}</span>
          <Button size="sm" variant="ghost" onClick={() => void vm.refresh()}>
            Réessayer
          </Button>
        </div>
      )}

      {sprintFocus && (
        <div className="mb-3 flex items-center justify-between gap-2 rounded-md border border-border bg-raised/50 px-3 py-2 text-xs text-muted">
          <span>
            Filtré sur le sprint « {vm.sprints.find((s) => s.id === sprintFocus)?.name ?? sprintFocus} »
          </span>
          <Button size="sm" variant="ghost" onClick={() => setSprintFocus(null)}>
            Voir tous les tickets
          </Button>
        </div>
      )}

      {/* ── Search + compact filters ── */}
      <div className="mb-3 flex flex-col gap-2">
        <Input
          aria-label="Rechercher des tickets"
          placeholder="Recherche…"
          value={vm.query.text ?? ""}
          onChange={(e) => vm.setQuery({ ...vm.query, text: e.target.value || undefined })}
        />
        <div className="flex flex-wrap items-center gap-2">
          <select
            aria-label="Filtrer par statut"
            className={selectClass}
            value={vm.query.statuses?.[0] ?? ""}
            onChange={(e) =>
              vm.setQuery({
                ...vm.query,
                statuses: e.target.value ? [e.target.value as (typeof TICKET_STATUSES)[number]] : undefined,
              })
            }
          >
            <option value="">Tous les statuts</option>
            {TICKET_STATUSES.map((s) => (
              <option key={s} value={s}>
                {webStatusLabel(s)}
              </option>
            ))}
          </select>
          <select
            aria-label="Filtrer par priorité"
            className={selectClass}
            value={vm.query.priorities?.[0] ?? ""}
            onChange={(e) =>
              vm.setQuery({
                ...vm.query,
                priorities: e.target.value
                  ? [e.target.value as (typeof TICKET_PRIORITIES)[number]]
                  : undefined,
              })
            }
          >
            <option value="">Toutes les priorités</option>
            {TICKET_PRIORITIES.map((p) => (
              <option key={p} value={p}>
                {webPriorityLabel(p)}
              </option>
            ))}
          </select>
          <select
            aria-label="Filtrer par agent assigné"
            className={selectClass}
            value={vm.query.assignedAgentId ?? ""}
            onChange={(e) =>
              vm.setQuery({ ...vm.query, assignedAgentId: e.target.value || undefined })
            }
          >
            <option value="">Tous les agents</option>
            {webAgents.agents.map((a) => (
              <option key={a.id} value={a.id}>
                {a.name}
              </option>
            ))}
          </select>
          <select
            aria-label="Trier les tickets"
            className={selectClass}
            value={vm.query.sort?.field ?? ""}
            onChange={(e) => {
              const field = e.target.value as TicketListSortField | "";
              const { sort: _drop, ...rest } = vm.query;
              vm.setQuery(field ? { ...rest, sort: { field, direction: "asc" } } : rest);
            }}
          >
            <option value="">Tri par défaut</option>
            {(Object.keys(SORT_FIELD_LABEL) as TicketListSortField[]).map((f) => (
              <option key={f} value={f}>
                {SORT_FIELD_LABEL[f]}
              </option>
            ))}
          </select>
        </div>
      </div>

      {/* ── List, grouped by sprint ── */}
      {vm.busy && vm.list === null ? (
        <span className="inline-flex items-center gap-1.5 text-sm text-muted">
          <Spinner size={12} /> Chargement des tickets…
        </span>
      ) : items.length === 0 && !hasFilters ? (
        <div className="flex flex-col items-start gap-2">
          <p className="text-sm text-muted">Aucun ticket dans ce projet.</p>
          <Button size="sm" onClick={() => setMode({ type: "create" })}>
            Créer un ticket
          </Button>
        </div>
      ) : items.length === 0 ? (
        <div className="flex flex-col items-start gap-2">
          <p className="text-sm text-muted">Aucun ticket ne correspond aux filtres.</p>
          <Button size="sm" variant="ghost" onClick={resetFilters}>
            Réinitialiser les filtres
          </Button>
        </div>
      ) : (
        <div className="flex flex-col gap-4" data-testid="web-ticket-list">
          {grouped.map((g) => (
            <TicketGroup
              key={g.sprint.id}
              heading={g.sprint.name}
              tickets={g.tickets}
              sprints={vm.sprints}
              nameOf={webAgents.nameOf}
              onOpen={(ref) => setMode({ type: "detail", ref })}
            />
          ))}
          {noSprint.length > 0 && (
            <TicketGroup
              heading="Sans sprint"
              tickets={noSprint}
              sprints={vm.sprints}
              nameOf={webAgents.nameOf}
              onOpen={(ref) => setMode({ type: "detail", ref })}
            />
          )}
        </div>
      )}

      {vm.list?.nextCursor && (
        <div className="mt-3 flex justify-center">
          <Button
            size="sm"
            variant="ghost"
            loading={vm.busy}
            onClick={() => vm.setQuery({ ...vm.query, limit: (vm.query.limit ?? 100) + 100 })}
          >
            Charger plus
          </Button>
        </div>
      )}
    </Panel>
  );
}

function TicketGroup({
  heading,
  tickets,
  sprints,
  nameOf,
  onOpen,
}: {
  heading: string;
  tickets: TicketSummary[];
  sprints: Sprint[];
  nameOf: (id: string) => string;
  onOpen: (ref: string) => void;
}) {
  const sprintNameOf = (id: string | null | undefined) =>
    id ? sprints.find((s) => s.id === id)?.name : undefined;
  return (
    <section aria-label={`Tickets — ${heading}`}>
      <h3 className="mb-1 flex items-center gap-2 text-xs font-semibold uppercase tracking-wide text-muted">
        {heading}
        <span className="rounded-full bg-raised px-1.5 text-[10px] font-medium text-muted">
          {tickets.length}
        </span>
      </h3>
      <ul className="flex flex-col divide-y divide-border">
        {tickets.map((t) => {
          const sprintName = sprintNameOf(t.sprintId);
          const assignees = t.assignedAgentIds.map(nameOf).join(", ");
          return (
            <li key={t.ref}>
              <button
                type="button"
                onClick={() => onOpen(t.ref)}
                aria-label={`${t.ref}, ${t.title}, statut ${webStatusLabel(t.status)}, priorité ${webPriorityLabel(t.priority)}`}
                className="flex w-full min-w-0 flex-col items-start gap-0.5 rounded-md px-2 py-2 text-left transition-colors hover:bg-raised"
              >
                <span className="flex w-full min-w-0 items-baseline gap-2">
                  <code className="shrink-0 rounded bg-raised px-1.5 py-0.5 font-mono text-xs text-content">
                    {t.ref}
                  </code>
                  <span className="min-w-0 flex-1 truncate text-sm font-medium text-content">
                    {t.title}
                  </span>
                </span>
                <span className="flex flex-wrap items-center gap-1.5 text-xs text-muted">
                  <WebStatusBadge status={t.status} />
                  <WebPriorityBadge priority={t.priority} />
                  {sprintName && <span>· {sprintName}</span>}
                  {assignees && <span>· {assignees}</span>}
                </span>
              </button>
            </li>
          );
        })}
      </ul>
    </section>
  );
}
