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

import { Button, Panel, Spinner, cn } from "@/shared";
import type { PermissionSet, PermissionPosture } from "@/domain";
import { McpToolPermissionsPanel } from "./McpToolPermissionsPanel";
import {
  type CapabilityChoice,
  type PolicyDraft,
  draftFromSet,
  isCustomPolicy,
  usePermissions,
} from "./usePermissions";

export interface PermissionsPanelProps {
  projectId: string;
}

type PermissionsTab = "system" | "mcpTools";

const TABS: { id: PermissionsTab; label: string }[] = [
  { id: "system", label: "Système" },
  { id: "mcpTools", label: "Tools MCP IdeA" },
];

type EditorTarget =
  | { type: "project" }
  | { type: "agent"; agentId: string; agentName: string };

const CAPABILITY_ROWS: {
  key: keyof Omit<PolicyDraft, "fallback">;
  label: string;
}[] = [
  { key: "read", label: "Read" },
  { key: "write", label: "Write" },
  { key: "delete", label: "Delete" },
  { key: "executeBash", label: "Bash" },
];

const POSTURE_LABELS: Record<PermissionPosture, string> = {
  ask: "Ask",
  allow: "Allow",
  deny: "Deny",
};

export function PermissionsPanel({ projectId }: PermissionsPanelProps) {
  const vm = usePermissions(projectId);
  const [target, setTarget] = useState<EditorTarget>({ type: "project" });
  const [tab, setTab] = useState<PermissionsTab>("system");

  const selectedAgent = target.type === "agent"
    ? vm.rows.find((row) => row.agent.id === target.agentId) ?? null
    : null;
  const activePolicy = selectedAgent?.override ?? vm.document?.projectDefaults ?? null;
  const activeDraft = target.type === "project"
    ? vm.projectDraft
    : draftFromSet(selectedAgent?.override ?? vm.document?.projectDefaults ?? null);
  const activeCustom = target.type === "project"
    ? isCustomPolicy(vm.document?.projectDefaults)
    : isCustomPolicy(selectedAgent?.override);
  const hasProjectDefaults = vm.document?.projectDefaults != null;

  async function handleSave(draft: PolicyDraft) {
    if (target.type === "project") {
      await vm.saveProjectDefaults(draft);
    } else {
      await vm.saveAgentOverride(target.agentId, draft);
    }
  }

  async function handleClear() {
    if (target.type === "project") {
      await vm.clearProjectDefaults();
    } else {
      await vm.clearAgentOverride(target.agentId);
    }
  }

  return (
    <Panel
      title="Permissions"
      actions={
        <Button
          size="sm"
          variant="ghost"
          disabled={vm.busy}
          onClick={() => void vm.refresh()}
        >
          Refresh
        </Button>
      }
      className="flex min-h-0 flex-1 flex-col"
      flush
    >
      <div role="tablist" aria-label="Permissions" className="flex gap-1 border-b border-border px-4 pt-2">
        {TABS.map((t) => (
          <button
            key={t.id}
            type="button"
            role="tab"
            id={`permissions-tab-${t.id}`}
            aria-selected={tab === t.id}
            aria-controls={`permissions-tabpanel-${t.id}`}
            tabIndex={tab === t.id ? 0 : -1}
            onClick={() => setTab(t.id)}
            onKeyDown={(e) => {
              if (e.key !== "ArrowRight" && e.key !== "ArrowLeft") return;
              e.preventDefault();
              const i = TABS.findIndex((x) => x.id === tab);
              const next = e.key === "ArrowRight" ? (i + 1) % TABS.length : (i - 1 + TABS.length) % TABS.length;
              setTab(TABS[next].id);
            }}
            className={cn(
              "min-h-[32px] rounded-t-md border-b-2 px-3 py-1.5 text-sm font-medium transition-colors",
              tab === t.id
                ? "border-primary text-content"
                : "border-transparent text-muted hover:text-content",
            )}
          >
            {t.label}
          </button>
        ))}
      </div>

      {vm.error && (
        <p
          role="alert"
          className="mx-4 mt-3 rounded-md border border-danger/40 bg-danger/10 px-3 py-2 text-sm text-danger"
        >
          {vm.error}
        </p>
      )}

      <div
        role="tabpanel"
        id="permissions-tabpanel-system"
        aria-labelledby="permissions-tab-system"
        hidden={tab !== "system"}
        className="flex flex-col gap-4 p-4"
      >
        <PolicyCard
          title="Project defaults"
          subtitle={hasProjectDefaults ? "Configured" : "Native CLI behavior"}
          active={target.type === "project"}
          custom={isCustomPolicy(vm.document?.projectDefaults)}
          onSelect={() => setTarget({ type: "project" })}
          policy={vm.document?.projectDefaults ?? null}
        />

        <div className="flex flex-col gap-2">
          <div className="flex items-center justify-between">
            <h4 className="text-xs font-semibold uppercase tracking-wide text-faint">
              Agents
            </h4>
            {vm.busy && <Spinner size={14} />}
          </div>
          {vm.rows.length === 0 ? (
            <p className="text-sm text-muted">No agents yet.</p>
          ) : (
            <ul className="flex flex-col gap-2">
              {vm.rows.map((row) => {
                const active =
                  target.type === "agent" && target.agentId === row.agent.id;
                return (
                  <li key={row.agent.id}>
                    <PolicyCard
                      title={row.agent.name}
                      subtitle={row.override ? "Override" : "Inherited"}
                      active={active}
                      custom={isCustomPolicy(row.override)}
                      onSelect={() =>
                        setTarget({
                          type: "agent",
                          agentId: row.agent.id,
                          agentName: row.agent.name,
                        })
                      }
                      policy={row.override}
                    />
                  </li>
                );
              })}
            </ul>
          )}
        </div>

        <PermissionEditor
          key={target.type === "project" ? "project" : target.agentId}
          title={
            target.type === "project"
              ? "Project defaults"
              : `Override — ${target.agentName}`
          }
          draft={activeDraft}
          sourcePolicy={activePolicy}
          custom={activeCustom}
          canClear={
            target.type === "project"
              ? vm.document?.projectDefaults != null
              : selectedAgent?.override != null
          }
          busy={vm.busy}
          onSave={(draft) => void handleSave(draft)}
          onClear={() => void handleClear()}
        />
      </div>

      <div
        role="tabpanel"
        id="permissions-tabpanel-mcpTools"
        aria-labelledby="permissions-tab-mcpTools"
        hidden={tab !== "mcpTools"}
        className="flex min-h-0 flex-1 flex-col"
      >
        {tab === "mcpTools" && (
          <McpToolPermissionsPanel projectId={projectId} agents={vm.agents} />
        )}
      </div>
    </Panel>
  );
}

interface PolicyCardProps {
  title: string;
  subtitle: string;
  active: boolean;
  custom: boolean;
  policy: PermissionSet | null;
  onSelect: () => void;
}

function PolicyCard({
  title,
  subtitle,
  active,
  custom,
  policy,
  onSelect,
}: PolicyCardProps) {
  return (
    <button
      type="button"
      onClick={onSelect}
      aria-pressed={active}
      className={cn(
        "flex w-full min-w-0 items-center justify-between gap-3 rounded-md border px-3 py-2 text-left",
        "transition-colors hover:border-border-strong hover:bg-raised",
        active ? "border-primary bg-raised" : "border-border bg-surface",
      )}
    >
      <span className="flex min-w-0 flex-col gap-0.5">
        <span className="truncate text-sm font-medium text-content">{title}</span>
        <span className="text-xs text-muted">{subtitle}</span>
      </span>
      <span className="flex shrink-0 items-center gap-1.5">
        {custom && (
          <span className="rounded-full bg-warning/15 px-2 py-0.5 text-xs font-medium text-warning">
            Custom
          </span>
        )}
        {policy && (
          <span className="rounded-full bg-primary/15 px-2 py-0.5 text-xs font-medium text-primary">
            {POSTURE_LABELS[policy.fallback]}
          </span>
        )}
      </span>
    </button>
  );
}

interface PermissionEditorProps {
  title: string;
  draft: PolicyDraft;
  sourcePolicy: PermissionSet | null;
  custom: boolean;
  canClear: boolean;
  busy: boolean;
  onSave: (draft: PolicyDraft) => void;
  onClear: () => void;
}

function PermissionEditor({
  title,
  draft,
  sourcePolicy,
  custom,
  canClear,
  busy,
  onSave,
  onClear,
}: PermissionEditorProps) {
  const [local, setLocal] = useState<PolicyDraft>(draft);
  // `draft` is a NEW object every time the underlying document changes for ANY
  // reason (initial load landing after mount, a Refresh, another tab's save…),
  // even when its content matches what is already in `local`. Naively
  // re-syncing on every `draft` change is a genuine race: if it fires after the
  // user has started editing (in production: a slow initial fetch landing
  // right as the user types; in tests: a passive effect flushing later than a
  // synchronous `fireEvent` sequence — ticket #79 flake), it silently discards
  // the user's in-progress edit. `prevDraftRef` tracks the last draft this
  // effect adopted `local` from; we only re-sync when `local` still matches it
  // (nothing has been edited since), so a legitimate incoming draft (first
  // load, or the echo of the user's own just-saved edit) is adopted, but an
  // edit in flight never gets clobbered.
  const prevDraftRef = useRef(draft);
  useEffect(() => {
    setLocal((current) =>
      JSON.stringify(current) === JSON.stringify(prevDraftRef.current)
        ? draft
        : current,
    );
    prevDraftRef.current = draft;
  }, [draft]);

  const changed = useMemo(
    () => JSON.stringify(local) !== JSON.stringify(draft),
    [draft, local],
  );

  function setCapability(
    capability: keyof Omit<PolicyDraft, "fallback">,
    choice: CapabilityChoice,
  ) {
    setLocal((prev) => ({ ...prev, [capability]: choice }));
  }

  return (
    <section className="rounded-md border border-border bg-surface">
      <header className="flex items-start justify-between gap-3 border-b border-border px-3 py-2.5">
        <div className="min-w-0">
          <h4 className="truncate text-sm font-semibold text-content">{title}</h4>
          <p className="text-xs text-muted">
            {sourcePolicy ? `${sourcePolicy.rules.length} rules` : "Not configured"}
          </p>
        </div>
        {custom && (
          <span className="shrink-0 rounded-full bg-warning/15 px-2 py-0.5 text-xs font-medium text-warning">
            Advanced
          </span>
        )}
      </header>

      <div className="flex flex-col gap-3 p-3">
        <label className="flex flex-col gap-1">
          <span className="text-xs font-medium text-muted">Fallback</span>
          <select
            aria-label={`${title} fallback`}
            value={local.fallback}
            disabled={busy}
            onChange={(e) =>
              setLocal((prev) => ({
                ...prev,
                fallback: e.target.value as PermissionPosture,
              }))
            }
            className={cn(
              "h-9 rounded-md border border-border bg-raised px-3 text-sm text-content",
              "outline-none transition-colors focus:border-primary disabled:cursor-not-allowed disabled:opacity-50",
            )}
          >
            <option value="ask">Ask</option>
            <option value="allow">Allow</option>
            <option value="deny">Deny</option>
          </select>
        </label>

        <div className="grid grid-cols-1 gap-2">
          {CAPABILITY_ROWS.map((row) => (
            <div
              key={row.key}
              className="grid grid-cols-[4.5rem_1fr] items-center gap-2"
            >
              <span className="text-xs font-medium text-muted">{row.label}</span>
              <SegmentedChoice
                value={local[row.key]}
                disabled={busy}
                label={row.label}
                onChange={(choice) => setCapability(row.key, choice)}
              />
            </div>
          ))}
        </div>

        <div className="flex flex-wrap items-center justify-end gap-2 pt-1">
          <Button
            size="sm"
            variant="ghost"
            disabled={busy || !canClear}
            onClick={onClear}
          >
            Clear
          </Button>
          <Button
            size="sm"
            variant="primary"
            disabled={busy || !changed}
            loading={busy && changed}
            onClick={() => onSave(local)}
          >
            Save
          </Button>
        </div>
      </div>
    </section>
  );
}

interface SegmentedChoiceProps {
  value: CapabilityChoice;
  disabled: boolean;
  label: string;
  onChange: (choice: CapabilityChoice) => void;
}

function SegmentedChoice({
  value,
  disabled,
  label,
  onChange,
}: SegmentedChoiceProps) {
  const choices: { value: CapabilityChoice; label: string }[] = [
    { value: "none", label: "None" },
    { value: "allow", label: "Allow" },
    { value: "deny", label: "Deny" },
  ];
  return (
    <div
      role="group"
      aria-label={`${label} permission`}
      className="grid grid-cols-3 overflow-hidden rounded-md border border-border"
    >
      {choices.map((choice) => (
        <button
          key={choice.value}
          type="button"
          disabled={disabled}
          aria-pressed={value === choice.value}
          onClick={() => onChange(choice.value)}
          className={cn(
            "h-8 min-w-0 border-r border-border px-2 text-xs font-medium last:border-r-0",
            "transition-colors disabled:cursor-not-allowed disabled:opacity-50",
            value === choice.value
              ? choice.value === "deny"
                ? "bg-danger/20 text-danger"
                : choice.value === "allow"
                  ? "bg-success/20 text-success"
                  : "bg-raised text-content"
              : "bg-surface text-muted hover:bg-raised hover:text-content",
          )}
        >
          {choice.label}
        </button>
      ))}
    </div>
  );
}
