/**
 * Read-only project work-state panel: one row per agent, showing live/offline
 * and idle/busy state from the backend read-model, plus the current input queue.
 */

import { Component, type ReactNode, useState } from "react";

import type {
  AgentTicketState,
  AgentWorkState,
  ConversationPreviewStatus,
  ConversationWorkSummary,
  LeafCell,
} from "@/domain";
import { useGateways } from "@/app/di";
import { leaves } from "@/features/layout/layout";
import { useLayout } from "@/features/layout/useLayout";
import type { LayoutViewModel } from "@/features/layout/useLayout";
import { Button, Panel, Spinner, cn } from "@/shared";
import { useProjectWorkState } from "./useProjectWorkState";

export interface ProjectWorkStatePanelProps {
  projectId: string;
}

interface WorkStatePanelErrorBoundaryProps {
  children: ReactNode;
}

interface WorkStatePanelErrorBoundaryState {
  error: Error | null;
  retryKey: number;
}

export class WorkStatePanelErrorBoundary extends Component<
  WorkStatePanelErrorBoundaryProps,
  WorkStatePanelErrorBoundaryState
> {
  state: WorkStatePanelErrorBoundaryState = { error: null, retryKey: 0 };

  static getDerivedStateFromError(error: Error): Partial<WorkStatePanelErrorBoundaryState> {
    return { error };
  }

  private retry = () => {
    this.setState(({ retryKey }) => ({ error: null, retryKey: retryKey + 1 }));
  };

  render() {
    if (this.state.error) {
      return (
        <Panel title="Work">
          <div
            role="alert"
            className="rounded-md border border-danger/40 bg-danger/10 px-3 py-2 text-sm text-danger"
          >
            <p className="font-medium">Work panel failed to render.</p>
            <p className="mt-1 text-xs text-danger/80">
              {this.state.error.message}
            </p>
            <Button
              size="sm"
              variant="ghost"
              className="mt-2"
              onClick={this.retry}
            >
              Retry
            </Button>
          </div>
        </Panel>
      );
    }

    return <div key={this.state.retryKey}>{this.props.children}</div>;
  }
}

function shortTicket(ticket: string): string {
  return ticket.length <= 8 ? ticket : ticket.slice(0, 8);
}

function formatStatus(status: AgentTicketState["status"]): string {
  return status === "inProgress" ? "In progress" : "Queued";
}

function requesterLabel(ticket: AgentTicketState): string {
  if (ticket.source.kind === "human") return "Human";
  const id = shortTicket(ticket.source.agentId);
  return ticket.requesterLabel.trim()
    ? `${ticket.requesterLabel} (${id})`
    : `Agent ${id}`;
}

function summaryLabel(status: ConversationPreviewStatus): string {
  switch (status) {
    case "ready":
      return "Summary";
    case "missing":
      return "No summary";
    case "partial":
      return "Partial";
    case "unavailable":
      return "Unavailable";
  }
}

function summaryText(summary: ConversationWorkSummary): string | null {
  if (summary.objectivePreview) return `Goal: ${summary.objectivePreview}`;
  if (summary.summaryPreview) return summary.summaryPreview;
  const lastTurn = (summary.recentTurns ?? []).at(-1);
  return lastTurn ? `Last: ${lastTurn.textPreview}` : null;
}

function goToCell(nodeId: string): void {
  if (typeof document === "undefined") return;
  const el = document.querySelector<HTMLElement>(`[data-node-id="${nodeId}"]`);
  if (!el) return;
  el.scrollIntoView?.({ block: "nearest", inline: "nearest" });
  const previous = el.style.outline;
  el.style.outline = "2px solid var(--color-primary, #5b9bd5)";
  window.setTimeout(() => {
    el.style.outline = previous;
  }, 1200);
}

async function copyToClipboard(text: string): Promise<void> {
  if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
    await navigator.clipboard.writeText(text);
    return;
  }
  throw new Error("Clipboard unavailable");
}

function ConversationSummaryLine({
  summary,
}: {
  summary: ConversationWorkSummary;
}) {
  const text = summaryText(summary);
  const [expanded, setExpanded] = useState(false);
  const [copied, setCopied] = useState(false);
  if (!text) return null;
  return (
    <div className="mt-1 min-w-0 pl-1 text-xs text-muted">
      <div className="flex min-w-0 items-start gap-2">
        <span
          className={cn(
            "mt-0.5 shrink-0 rounded-full px-1.5 py-0.5 font-medium",
            summary.status === "ready"
              ? "bg-success/10 text-success"
              : "bg-raised text-muted",
          )}
        >
          {summaryLabel(summary.status)}
        </span>
        <span className="min-w-0 flex-1 break-words">{text}</span>
        <button
          type="button"
          className="shrink-0 rounded px-1 py-0.5 text-[11px] text-muted hover:bg-raised hover:text-content"
          onClick={() => setExpanded((v) => !v)}
        >
          {expanded ? "Hide" : "View"}
        </button>
        <button
          type="button"
          className="shrink-0 rounded px-1 py-0.5 text-[11px] text-muted hover:bg-raised hover:text-content disabled:opacity-50"
          disabled={!text}
          onClick={() => {
            void copyToClipboard(text).then(() => {
              setCopied(true);
              window.setTimeout(() => setCopied(false), 1000);
            });
          }}
        >
          {copied ? "Copied" : "Copy"}
        </button>
      </div>
      {expanded && (
        <div
          aria-label={`conversation ${summary.conversationId} details`}
          className="mt-1 space-y-1 border-l border-border pl-2"
        >
          {summary.objectivePreview && (
            <p>
              <span className="font-medium text-content">Goal:</span>{" "}
              {summary.objectivePreview}
            </p>
          )}
          {summary.summaryPreview && (
            <p>
              <span className="font-medium text-content">Summary:</span>{" "}
              {summary.summaryPreview}
            </p>
          )}
          {(summary.recentTurns ?? []).length > 0 && (
            <ul className="space-y-0.5">
              {(summary.recentTurns ?? []).map((turn) => (
                <li key={`${turn.atMs}-${turn.role}-${turn.textPreview}`}>
                  <span className="font-medium text-content">{turn.role}:</span>{" "}
                  {turn.textPreview}
                </li>
              ))}
            </ul>
          )}
        </div>
      )}
    </div>
  );
}

function TicketRow({
  ticket,
  summary,
}: {
  ticket: AgentTicketState;
  summary?: ConversationWorkSummary;
}) {
  const truncated = ticket.taskLen > ticket.taskPreview.length;
  return (
    <li className="min-w-0 text-xs text-muted">
      <div className="flex min-w-0 items-start gap-2">
        <span
          className={cn(
            "mt-0.5 shrink-0 rounded-full px-1.5 py-0.5 font-medium",
            ticket.status === "inProgress"
              ? "bg-warning/15 text-warning"
              : "bg-raised text-muted",
          )}
        >
          #{ticket.position + 1} {formatStatus(ticket.status)}
        </span>
        <span className="min-w-0 flex-1">
          <span className="font-medium text-content">{requesterLabel(ticket)}</span>
          <span className="text-muted"> · </span>
          <span className="break-words">{ticket.taskPreview}</span>
          {truncated && (
            <span
              aria-label={`${ticket.taskLen - ticket.taskPreview.length} more characters`}
              className="text-muted"
            >
              {" "}
              +{ticket.taskLen - ticket.taskPreview.length}
            </span>
          )}
        </span>
        <code
          aria-label={`ticket ${shortTicket(ticket.ticketId)}`}
          title={ticket.ticketId}
          className="mt-0.5 shrink-0 rounded bg-raised px-1.5 py-0.5 text-[11px] text-muted"
        >
          {shortTicket(ticket.ticketId)}
        </code>
      </div>
      {summary && <ConversationSummaryLine summary={summary} />}
    </li>
  );
}

function AgentRow({
  agent,
  conversations,
  visibleLeaves,
  layout,
  projectId,
  onRefresh,
}: {
  agent: AgentWorkState;
  conversations: Map<string, ConversationWorkSummary>;
  visibleLeaves: LeafCell[];
  layout: LayoutViewModel;
  projectId: string;
  onRefresh: () => Promise<void>;
}) {
  const { agent: agentGateway } = useGateways();
  const [message, setMessage] = useState<string | null>(null);
  const [actionBusy, setActionBusy] = useState(false);
  const live = agent.live !== undefined;
  const busyState = agent.busy ?? { state: "idle" };
  const busy = busyState.state === "busy";
  const tickets = [...(agent.tickets ?? [])].sort((a, b) => a.position - b.position);
  const visibleNodeIds = new Set(visibleLeaves.map((leaf) => leaf.id));
  const liveVisible = live && visibleNodeIds.has(agent.live!.nodeId);
  const attachTarget = visibleLeaves.find((leaf) => !leaf.session && !leaf.agent);
  const currentTurn =
    busy || tickets.some((ticket) => ticket.status === "inProgress");

  async function refreshAfterAction(): Promise<void> {
    await onRefresh();
  }

  async function attach(): Promise<void> {
    if (!agent.live || !attachTarget || !agentGateway?.attachLiveAgent) return;
    setActionBusy(true);
    setMessage(null);
    try {
      const attached = await agentGateway.attachLiveAgent(
        projectId,
        agent.agentId,
        attachTarget.id,
      );
      await layout.attachLiveAgentToCell(
        attachTarget.id,
        agent.agentId,
        attached.sessionId,
      );
      await refreshAfterAction();
      goToCell(attachTarget.id);
    } catch (e) {
      setMessage(e instanceof Error ? e.message : String(e));
    } finally {
      setActionBusy(false);
    }
  }

  async function stop(): Promise<void> {
    if (!agent.live || !agentGateway?.stopLiveAgent) return;
    const warning = currentTurn
      ? " The current turn will be interrupted."
      : "";
    if (!window.confirm(`Stop ${agent.name}?${warning}`)) return;
    setActionBusy(true);
    setMessage(null);
    try {
      const stopped = await agentGateway.stopLiveAgent(projectId, agent.agentId);
      if (visibleNodeIds.has(agent.live.nodeId)) {
        const leaf = visibleLeaves.find((candidate) => candidate.id === agent.live?.nodeId);
        if (!leaf?.session || leaf.session === stopped.sessionId) {
          await layout.setSession(agent.live.nodeId, null);
        }
      }
      await refreshAfterAction();
    } catch (e) {
      setMessage(e instanceof Error ? e.message : String(e));
    } finally {
      setActionBusy(false);
    }
  }

  return (
    <li className="py-2 first:pt-0 last:pb-0">
      <div className="flex items-center justify-between gap-3">
        <span className="flex min-w-0 flex-col gap-0.5">
          <span className="truncate text-sm font-medium text-content">
            {agent.name}
          </span>
          <code className="truncate text-xs text-muted">{agent.profileId}</code>
        </span>
        <span className="flex shrink-0 items-center gap-1.5">
          <span
            className={cn(
              "rounded-full px-2 py-0.5 text-xs font-medium",
              live
                ? "bg-success/15 text-success"
                : "bg-raised text-muted",
            )}
          >
            {live ? "Live" : "Offline"}
          </span>
          <span
            className={cn(
              "rounded-full px-2 py-0.5 text-xs font-medium",
              busy
                ? "bg-warning/15 text-warning"
                : "bg-raised text-muted",
            )}
          >
            {busy ? "Busy" : "Idle"}
          </span>
          {busyState.state === "busy" && (
            <code
              aria-label={`busy ticket ${shortTicket(busyState.ticket)}`}
              title={busyState.ticket}
              className="rounded bg-raised px-1.5 py-0.5 text-xs text-muted"
            >
              {shortTicket(busyState.ticket)}
            </code>
          )}
          {agent.live && liveVisible && (
            <Button
              size="sm"
              variant="ghost"
              onClick={() => goToCell(agent.live!.nodeId)}
            >
              Open
            </Button>
          )}
          {agent.live && !liveVisible && (
            <Button
              size="sm"
              variant="ghost"
              disabled={!attachTarget || !agentGateway?.attachLiveAgent}
              loading={actionBusy}
              title={
                attachTarget
                  ? undefined
                  : "No empty visible cell available"
              }
              onClick={() => void attach()}
            >
              Attach
            </Button>
          )}
          {agent.live && (
            <Button
              size="sm"
              variant="danger"
              loading={actionBusy}
              disabled={!agentGateway?.stopLiveAgent}
              onClick={() => void stop()}
            >
              Stop
            </Button>
          )}
        </span>
      </div>
      {agent.live && !liveVisible && !attachTarget && (
        <p className="mt-1 text-xs text-muted">No empty visible cell.</p>
      )}
      {message && (
        <p role="alert" className="mt-1 text-xs text-danger">
          {message}
        </p>
      )}
      {tickets.length > 0 && (
        <ul
          aria-label={`${agent.name} tickets`}
          className="mt-2 flex flex-col gap-1"
        >
          {tickets.map((ticket) => (
            <TicketRow
              key={ticket.ticketId}
              ticket={ticket}
              summary={conversations.get(ticket.conversationId)}
            />
          ))}
        </ul>
      )}
    </li>
  );
}

function ProjectWorkStatePanelContent({ projectId }: ProjectWorkStatePanelProps) {
  const vm = useProjectWorkState(projectId);
  const layoutVm = useLayout(projectId);
  const agents = vm.state?.agents ?? [];
  const visibleLeaves = layoutVm.layout ? leaves(layoutVm.layout) : [];
  const conversations = new Map(
    (vm.state?.conversations ?? []).map((summary) => [
      summary.conversationId,
      summary,
    ]),
  );

  return (
    <Panel
      title="Work"
      actions={
        <Button
          size="sm"
          variant="ghost"
          onClick={() => void vm.refresh()}
          loading={vm.busy}
        >
          Refresh
        </Button>
      }
    >
      {vm.error && (
        <p
          role="alert"
          className="mb-3 rounded-md border border-danger/40 bg-danger/10 px-3 py-2 text-sm text-danger"
        >
          {vm.error}
        </p>
      )}
      {vm.busy && vm.state === null ? (
        <div className="flex items-center gap-2 text-sm text-muted">
          <Spinner size={14} />
          <span>Loading work state…</span>
        </div>
      ) : agents.length === 0 ? (
        <p className="text-sm text-muted">No agent work state.</p>
      ) : (
        <ul className="flex flex-col divide-y divide-border">
          {agents.map((agent) => (
            <AgentRow
              key={agent.agentId}
              agent={agent}
              conversations={conversations}
              visibleLeaves={visibleLeaves}
              layout={layoutVm}
              projectId={projectId}
              onRefresh={vm.refresh}
            />
          ))}
        </ul>
      )}
    </Panel>
  );
}

export function ProjectWorkStatePanel(props: ProjectWorkStatePanelProps) {
  return (
    <WorkStatePanelErrorBoundary>
      <ProjectWorkStatePanelContent {...props} />
    </WorkStatePanelErrorBoundary>
  );
}
