import { useEffect, useMemo, useRef, useState } from "react";
import {
  Archive,
  Check,
  ChevronRight,
  Pencil,
  Plus,
  RefreshCw,
  Save,
  Send,
  Trash2
} from "lucide-react";
import {
  AdminPageLayout,
  Button,
  ConfirmDialog,
  Dialog,
  DismissibleAlert,
  FormField,
  IconButton,
  StatusBadge,
  ToggleSwitch,
  adminErrorMessage,
  dispatchPlatformViewChanged,
  useUnsavedChanges,
  useUnsavedDraftGuard,
  useViewSurfaces,
  type ApiSettings,
  type PlatformViewSurface
} from "@govoplan/core-webui";
import {
  archiveViewDefinition,
  createViewAssignment,
  createViewDefinition,
  createViewRevision,
  deleteViewAssignment,
  fetchViewAssignments,
  fetchViewDefinitions,
  publishViewRevision,
  updateViewAssignment,
  updateViewDefinition,
  type ViewAssignment,
  type ViewAssignmentMode,
  type ViewAssignmentScopeType,
  type ViewDefinition,
  type ViewScopeType
} from "../../api/views";


type DefinitionDraft = {
  name: string;
  description: string;
  surfaceIds: string[];
};

type AssignmentDraft = {
  scopeType: ViewAssignmentScopeType;
  scopeId: string;
  definitionId: string;
  mode: ViewAssignmentMode;
  priority: number;
  active: boolean;
  pinRevision: boolean;
};

const LOCKOUT_SURFACES = new Set([
  "access.module",
  "access.nav.admin",
  "access.route.admin",
  "views.module",
  "views.selector",
  "views.admin.system",
  "views.admin.tenant"
]);


export default function ViewsAdminPanel({
  settings,
  scopeType,
  canWriteDefinitions,
  canWriteAssignments
}: {
  settings: ApiSettings;
  scopeType: ViewScopeType;
  canWriteDefinitions: boolean;
  canWriteAssignments: boolean;
}) {
  const surfaces = useViewSurfaces();
  const { requestDiscard } = useUnsavedChanges();
  const [definitions, setDefinitions] = useState<ViewDefinition[]>([]);
  const [assignments, setAssignments] = useState<ViewAssignment[]>([]);
  const [selectedId, setSelectedId] = useState("");
  const [draft, setDraft] = useState<DefinitionDraft>({
    name: "",
    description: "",
    surfaceIds: []
  });
  const [savedDraftKey, setSavedDraftKey] = useState("");
  const [loading, setLoading] = useState(true);
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState("");
  const [success, setSuccess] = useState("");
  const [createOpen, setCreateOpen] = useState(false);
  const [createDraft, setCreateDraft] = useState({
    name: "",
    description: ""
  });
  const [assignmentEditor, setAssignmentEditor] = useState<
    ViewAssignment | "new" | null
  >(null);
  const [assignmentDraft, setAssignmentDraft] = useState<AssignmentDraft>(
    emptyAssignmentDraft(scopeType)
  );
  const [archiveTarget, setArchiveTarget] = useState<ViewDefinition | null>(null);
  const [deleteAssignmentTarget, setDeleteAssignmentTarget] =
    useState<ViewAssignment | null>(null);

  const selected = definitions.find((item) => item.id === selectedId) ?? null;
  const dirty = Boolean(
    selected &&
    !selected.readonly &&
    definitionDraftKey(draft) !== savedDraftKey
  );

  useUnsavedDraftGuard({
    dirty,
    onSave: saveDraft,
    onDiscard: resetDraft
  });

  async function load(preferredId?: string) {
    setLoading(true);
    setError("");
    try {
      const [nextDefinitions, nextAssignments] = await Promise.all([
        fetchViewDefinitions(settings, scopeType),
        fetchViewAssignments(settings, scopeType)
      ]);
      setDefinitions(nextDefinitions);
      setAssignments(nextAssignments);
      const nextSelectedId =
        preferredId && nextDefinitions.some((item) => item.id === preferredId)
          ? preferredId
          : nextDefinitions.some((item) => item.id === selectedId)
            ? selectedId
            : nextDefinitions[0]?.id ?? "";
      setSelectedId(nextSelectedId);
      const nextSelected =
        nextDefinitions.find((item) => item.id === nextSelectedId) ?? null;
      applyDefinitionDraft(nextSelected);
    } catch (caught) {
      setError(adminErrorMessage(caught));
    } finally {
      setLoading(false);
    }
  }

  useEffect(() => {
    setSelectedId("");
    setAssignmentDraft(emptyAssignmentDraft(scopeType));
    void load();
  }, [
    scopeType,
    settings.accessToken,
    settings.apiBaseUrl,
    settings.apiKey
  ]);

  function applyDefinitionDraft(definition: ViewDefinition | null) {
    const next = definition
      ? {
          name: definition.name,
          description: definition.description ?? "",
          surfaceIds: definition.latest_revision.visible_surface_ids
        }
      : { name: "", description: "", surfaceIds: [] };
    setDraft(next);
    setSavedDraftKey(definitionDraftKey(next));
  }

  function resetDraft() {
    applyDefinitionDraft(selected);
  }

  function selectDefinition(definitionId: string) {
    if (definitionId === selectedId) return;
    requestDiscard(() => {
      const definition =
        definitions.find((item) => item.id === definitionId) ?? null;
      setSelectedId(definitionId);
      applyDefinitionDraft(definition);
      setError("");
      setSuccess("");
    });
  }

  async function persistDraft(): Promise<ViewDefinition | null> {
    if (!selected || selected.readonly || selected.status === "archived") {
      return selected;
    }
    setBusy(true);
    setError("");
    try {
      let next = selected;
      if (
        draft.name.trim() !== selected.name ||
        draft.description.trim() !== (selected.description ?? "")
      ) {
        next = await updateViewDefinition(settings, selected.id, {
          name: draft.name.trim(),
          description: draft.description.trim() || null
        });
      }
      if (
        surfaceSetKey(draft.surfaceIds) !==
        surfaceSetKey(next.latest_revision.visible_surface_ids)
      ) {
        next = await createViewRevision(
          settings,
          selected.id,
          draft.surfaceIds
        );
      }
      await load(selected.id);
      return next;
    } catch (caught) {
      setError(adminErrorMessage(caught));
      return null;
    } finally {
      setBusy(false);
    }
  }

  async function saveDraft(): Promise<boolean> {
    const next = await persistDraft();
    if (!next) return false;
    setSuccess("View draft saved.");
    return true;
  }

  async function publishDraft() {
    const persisted = await persistDraft();
    if (!persisted) return;
    setBusy(true);
    setError("");
    try {
      await publishViewRevision(
        settings,
        persisted.id,
        persisted.latest_revision.id
      );
      setSuccess(`Revision ${persisted.latest_revision.revision} published.`);
      await load(persisted.id);
      dispatchPlatformViewChanged();
    } catch (caught) {
      setError(adminErrorMessage(caught));
    } finally {
      setBusy(false);
    }
  }

  async function createDefinition() {
    setBusy(true);
    setError("");
    try {
      const visibleSurfaceIds = surfaces
        .filter((surface) => surface.defaultVisible !== false)
        .map((surface) => surface.id);
      const created = await createViewDefinition(settings, {
        scope_type: scopeType,
        name: createDraft.name.trim(),
        description: createDraft.description.trim() || null,
        visible_surface_ids: visibleSurfaceIds
      });
      setCreateOpen(false);
      setCreateDraft({ name: "", description: "" });
      setSuccess("View draft created with all currently available surfaces.");
      await load(created.id);
    } catch (caught) {
      setError(adminErrorMessage(caught));
    } finally {
      setBusy(false);
    }
  }

  async function archiveDefinition() {
    if (!archiveTarget) return;
    setBusy(true);
    setError("");
    try {
      await archiveViewDefinition(settings, archiveTarget.id);
      setArchiveTarget(null);
      setSuccess("View archived.");
      await load();
      dispatchPlatformViewChanged();
    } catch (caught) {
      setError(adminErrorMessage(caught));
    } finally {
      setBusy(false);
    }
  }

  function openCreateAssignment() {
    const firstPublished = definitions.find(
      (definition) => definition.status === "published"
    );
    setAssignmentDraft({
      ...emptyAssignmentDraft(scopeType),
      definitionId: firstPublished?.id ?? ""
    });
    setAssignmentEditor("new");
    setError("");
  }

  function openEditAssignment(assignment: ViewAssignment) {
    setAssignmentDraft({
      scopeType: assignment.scope_type,
      scopeId: assignment.scope_id ?? "",
      definitionId: assignment.definition_id,
      mode: assignment.mode,
      priority: assignment.priority,
      active: assignment.is_active,
      pinRevision: Boolean(assignment.revision_id)
    });
    setAssignmentEditor(assignment);
    setError("");
  }

  async function saveAssignment() {
    const definition = definitions.find(
      (item) => item.id === assignmentDraft.definitionId
    );
    if (!definition) return;
    setBusy(true);
    setError("");
    try {
      const revisionId = assignmentDraft.pinRevision
        ? (
            assignmentEditor !== "new" &&
            assignmentEditor?.revision_id
              ? assignmentEditor.revision_id
              : definition.published_revision?.id ?? null
          )
        : null;
      if (assignmentEditor === "new") {
        await createViewAssignment(settings, {
          scope_type: assignmentDraft.scopeType,
          scope_id: assignmentDraft.scopeId.trim() || null,
          definition_id: definition.id,
          revision_id: revisionId,
          mode: assignmentDraft.mode,
          priority: assignmentDraft.priority,
          is_active: assignmentDraft.active
        });
      } else if (assignmentEditor) {
        await updateViewAssignment(settings, assignmentEditor.id, {
          revision_id: revisionId,
          mode: assignmentDraft.mode,
          priority: assignmentDraft.priority,
          is_active: assignmentDraft.active
        });
      }
      setAssignmentEditor(null);
      setSuccess(
        assignmentEditor === "new"
          ? "View assignment created."
          : "View assignment updated."
      );
      await load(selectedId);
      dispatchPlatformViewChanged();
    } catch (caught) {
      setError(adminErrorMessage(caught));
    } finally {
      setBusy(false);
    }
  }

  async function toggleAssignment(
    assignment: ViewAssignment,
    active: boolean
  ) {
    setBusy(true);
    setError("");
    try {
      await updateViewAssignment(settings, assignment.id, {
        is_active: active
      });
      await load(selectedId);
      dispatchPlatformViewChanged();
    } catch (caught) {
      setError(adminErrorMessage(caught));
    } finally {
      setBusy(false);
    }
  }

  async function removeAssignment() {
    if (!deleteAssignmentTarget) return;
    setBusy(true);
    setError("");
    try {
      await deleteViewAssignment(settings, deleteAssignmentTarget.id);
      setDeleteAssignmentTarget(null);
      setSuccess("View assignment removed.");
      await load(selectedId);
      dispatchPlatformViewChanged();
    } catch (caught) {
      setError(adminErrorMessage(caught));
    } finally {
      setBusy(false);
    }
  }

  const title = scopeType === "system" ? "System Views" : "Tenant Views";
  const description =
    scopeType === "system"
      ? "Publish reusable interface projections and assign instance-wide defaults or requirements."
      : "Tailor the visible interface for this tenant, its groups, and individual users.";
  const canCreate =
    canWriteDefinitions &&
    surfaces.some((surface) => surface.kind === "navigation") &&
    surfaces.some((surface) => surface.kind === "route");

  return (
    <>
      <AdminPageLayout
        title={title}
        description={description}
        loading={loading}
        error={error}
        success={success}
        actions={
          <>
            <IconButton
              label="Reload Views"
              icon={<RefreshCw />}
              onClick={() => requestDiscard(() => void load(selectedId))}
              disabled={loading || busy}
            />
            <Button
              variant="primary"
              onClick={() => setCreateOpen(true)}
              disabled={!canCreate || busy}
              disabledReason={
                canWriteDefinitions
                  ? undefined
                  : "You do not have permission to create Views."
              }
            >
              <Plus size={16} aria-hidden="true" />
              New View
            </Button>
          </>
        }
      >
        <div className="views-admin-shell">
          <aside className="views-definition-pane" aria-label="View definitions">
            <div className="views-pane-heading">
              <strong>Definitions</strong>
              <span>{definitions.length}</span>
            </div>
            <div className="views-definition-list">
              {definitions.map((definition) => (
                <button
                  key={definition.id}
                  type="button"
                  className={
                    definition.id === selectedId
                      ? "views-definition-item active"
                      : "views-definition-item"
                  }
                  onClick={() => selectDefinition(definition.id)}
                >
                  <span className="views-definition-item-main">
                    <strong>{definition.name}</strong>
                    <small>
                      {definition.scope_type === "system" ? "System" : "Tenant"}
                      {" · "}
                      revision {definition.current_revision}
                    </small>
                  </span>
                  <StatusBadge status={definition.status} />
                  <ChevronRight size={16} aria-hidden="true" />
                </button>
              ))}
              {!definitions.length && (
                <div className="views-empty-list">
                  No Views have been defined in this scope.
                </div>
              )}
            </div>
          </aside>

          <main className="views-editor-pane">
            {selected ? (
              <>
                <div className="views-editor-heading">
                  <div>
                    <div className="views-editor-title-row">
                      <h3>{selected.name}</h3>
                      {selected.readonly && (
                        <StatusBadge status="inherited" label="Inherited" />
                      )}
                    </div>
                    <p className="muted small-note">
                      Draft revision {selected.latest_revision.revision}
                      {selected.published_revision
                        ? ` · published revision ${selected.published_revision.revision}`
                        : " · not published"}
                    </p>
                  </div>
                  {!selected.readonly && selected.status !== "archived" && (
                    <div className="button-row compact-actions">
                      <Button onClick={resetDraft} disabled={!dirty || busy}>
                        Discard
                      </Button>
                      <Button
                        onClick={() => void saveDraft()}
                        disabled={!dirty || busy}
                      >
                        <Save size={16} aria-hidden="true" />
                        Save revision
                      </Button>
                      <Button
                        variant="primary"
                        onClick={() => void publishDraft()}
                        disabled={busy || !draft.name.trim()}
                      >
                        <Send size={16} aria-hidden="true" />
                        Publish
                      </Button>
                      <IconButton
                        label="Archive View"
                        icon={<Archive />}
                        variant="danger"
                        onClick={() => setArchiveTarget(selected)}
                        disabled={busy}
                      />
                    </div>
                  )}
                </div>

                <div className="views-definition-form">
                  <FormField label="Name">
                    <input
                      value={draft.name}
                      maxLength={200}
                      disabled={selected.readonly || selected.status === "archived"}
                      onChange={(event) =>
                        setDraft({ ...draft, name: event.target.value })
                      }
                    />
                  </FormField>
                  <FormField label="Description">
                    <textarea
                      rows={2}
                      value={draft.description}
                      maxLength={4000}
                      disabled={selected.readonly || selected.status === "archived"}
                      onChange={(event) =>
                        setDraft({ ...draft, description: event.target.value })
                      }
                    />
                  </FormField>
                </div>

                <section className="views-surface-section">
                  <div className="views-section-heading">
                    <div>
                      <h4>Visible surfaces</h4>
                      <p className="muted small-note">
                        Permission checks remain active even when a surface is
                        visible. Parent surfaces are retained automatically.
                      </p>
                    </div>
                    <strong>{draft.surfaceIds.length} selected</strong>
                  </div>
                  {selected.stale_surface_ids.length > 0 && (
                    <DismissibleAlert tone="warning" dismissible={false}>
                      <div className="views-stale-surface-warning">
                        <span>
                          This revision references retired or currently unavailable
                          surfaces: {selected.stale_surface_ids.join(", ")}
                        </span>
                        {!selected.readonly && selected.status !== "archived" && (
                          <Button
                            onClick={() =>
                              setDraft({
                                ...draft,
                                surfaceIds: draft.surfaceIds.filter(
                                  (id) => !selected.stale_surface_ids.includes(id)
                                )
                              })
                            }
                            disabled={busy}
                          >
                            Remove stale references
                          </Button>
                        )}
                      </div>
                    </DismissibleAlert>
                  )}
                  <SurfaceSelector
                    surfaces={surfaces}
                    selected={draft.surfaceIds}
                    disabled={selected.readonly || selected.status === "archived"}
                    onChange={(surfaceIds) =>
                      setDraft({ ...draft, surfaceIds })
                    }
                  />
                </section>

                <AssignmentsSection
                  assignments={assignments}
                  definitions={definitions}
                  scopeType={scopeType}
                  busy={busy}
                  canWrite={canWriteAssignments}
                  onCreate={openCreateAssignment}
                  onEdit={openEditAssignment}
                  onToggle={toggleAssignment}
                  onDelete={setDeleteAssignmentTarget}
                />
              </>
            ) : (
              <div className="views-empty-editor">
                <h3>No View selected</h3>
                <p>Create a View to define a focused interface projection.</p>
              </div>
            )}
          </main>
        </div>
      </AdminPageLayout>

      <Dialog
        open={createOpen}
        title="Create View"
        onClose={() => setCreateOpen(false)}
        closeDisabled={busy}
        footer={
          <>
            <Button onClick={() => setCreateOpen(false)} disabled={busy}>
              Cancel
            </Button>
            <Button
              variant="primary"
              onClick={() => void createDefinition()}
              disabled={busy || !createDraft.name.trim()}
            >
              Create
            </Button>
          </>
        }
      >
        <div className="form-grid">
          <FormField label="Name">
            <input
              autoFocus
              value={createDraft.name}
              maxLength={200}
              onChange={(event) =>
                setCreateDraft({ ...createDraft, name: event.target.value })
              }
            />
          </FormField>
          <FormField label="Description">
            <textarea
              rows={4}
              value={createDraft.description}
              maxLength={4000}
              onChange={(event) =>
                setCreateDraft({
                  ...createDraft,
                  description: event.target.value
                })
              }
            />
          </FormField>
          <p className="muted small-note">
            The first draft starts with all current surfaces visible. Refine
            the selection before publishing it.
          </p>
        </div>
      </Dialog>

      <AssignmentDialog
        open={assignmentEditor !== null}
        editing={assignmentEditor}
        draft={assignmentDraft}
        definitions={definitions}
        scopeType={scopeType}
        busy={busy}
        onChange={setAssignmentDraft}
        onClose={() => setAssignmentEditor(null)}
        onSave={saveAssignment}
      />

      <ConfirmDialog
        open={archiveTarget !== null}
        title="Archive View"
        message={
          archiveTarget
            ? `Archive "${archiveTarget.name}"? Existing optional assignments will be deactivated. Required assignments must be removed first.`
            : ""
        }
        confirmLabel="Archive"
        tone="danger"
        busy={busy}
        onCancel={() => setArchiveTarget(null)}
        onConfirm={() => void archiveDefinition()}
      />

      <ConfirmDialog
        open={deleteAssignmentTarget !== null}
        title="Remove View assignment"
        message="The target will no longer inherit this available, default, or required View from this assignment."
        confirmLabel="Remove"
        tone="danger"
        busy={busy}
        onCancel={() => setDeleteAssignmentTarget(null)}
        onConfirm={() => void removeAssignment()}
      />
    </>
  );
}


function AssignmentsSection({
  assignments,
  definitions,
  scopeType,
  busy,
  canWrite,
  onCreate,
  onEdit,
  onToggle,
  onDelete
}: {
  assignments: ViewAssignment[];
  definitions: ViewDefinition[];
  scopeType: ViewScopeType;
  busy: boolean;
  canWrite: boolean;
  onCreate: () => void;
  onEdit: (assignment: ViewAssignment) => void;
  onToggle: (assignment: ViewAssignment, active: boolean) => void;
  onDelete: (assignment: ViewAssignment) => void;
}) {
  const definitionById = useMemo(
    () => new Map(definitions.map((definition) => [definition.id, definition])),
    [definitions]
  );

  return (
    <section className="views-assignments-section">
      <div className="views-section-heading">
        <div>
          <h4>Assignments</h4>
          <p className="muted small-note">
            Higher-specificity user and group assignments take precedence over
            tenant and system assignments. Required assignments cannot be left.
          </p>
        </div>
        <Button
          onClick={onCreate}
          disabled={!canWrite || busy || !definitions.some((item) => item.status === "published")}
        >
          <Plus size={16} aria-hidden="true" />
          Add assignment
        </Button>
      </div>
      <div className="views-assignment-list">
        {assignments.map((assignment) => {
          const inherited =
            scopeType === "tenant" && assignment.tenant_id == null;
          return (
            <div key={assignment.id} className="views-assignment-row">
              <div className="views-assignment-view">
                <strong>
                  {definitionById.get(assignment.definition_id)?.name ??
                    assignment.definition_id}
                </strong>
                <span>
                  {assignment.scope_type}
                  {assignment.scope_id ? ` · ${assignment.scope_id}` : ""}
                </span>
              </div>
              <StatusBadge status={assignment.mode} />
              <span className="views-assignment-priority">
                Priority {assignment.priority}
              </span>
              <span className="views-assignment-revision">
                {assignment.revision_id ? "Pinned revision" : "Tracks published"}
              </span>
              <ToggleSwitch
                label="Assignment active"
                checked={assignment.is_active}
                disabled={inherited || !canWrite || busy}
                onChange={(active) => onToggle(assignment, active)}
              />
              <div className="button-row compact-actions">
                <IconButton
                  label="Edit assignment"
                  icon={<Pencil />}
                  onClick={() => onEdit(assignment)}
                  disabled={inherited || !canWrite || busy}
                />
                <IconButton
                  label="Remove assignment"
                  icon={<Trash2 />}
                  variant="danger"
                  onClick={() => onDelete(assignment)}
                  disabled={inherited || !canWrite || busy}
                />
              </div>
            </div>
          );
        })}
        {!assignments.length && (
          <div className="views-empty-list">
            No View assignments exist in this scope.
          </div>
        )}
      </div>
    </section>
  );
}


function AssignmentDialog({
  open,
  editing,
  draft,
  definitions,
  scopeType,
  busy,
  onChange,
  onClose,
  onSave
}: {
  open: boolean;
  editing: ViewAssignment | "new" | null;
  draft: AssignmentDraft;
  definitions: ViewDefinition[];
  scopeType: ViewScopeType;
  busy: boolean;
  onChange: (draft: AssignmentDraft) => void;
  onClose: () => void;
  onSave: () => void;
}) {
  const definition = definitions.find(
    (item) => item.id === draft.definitionId
  );
  const requiredAdminSurface =
    draft.scopeType === "system"
      ? "views.admin.system"
      : "views.admin.tenant";
  const validatesCurrentPublishedRevision =
    editing === "new" ||
    !draft.pinRevision ||
    !editing?.revision_id;
  const requiredMissing = draft.mode === "required"
    ? [...LOCKOUT_SURFACES]
        .filter((id) => id !== "views.admin.system" && id !== "views.admin.tenant")
        .concat(requiredAdminSurface)
        .filter(
          (id) =>
            validatesCurrentPublishedRevision &&
            definitions.length > 0 &&
            definition?.published_revision &&
            !definition.published_revision.visible_surface_ids.includes(id)
        )
    : [];
  const targetRequired =
    draft.scopeType === "group" || draft.scopeType === "user";
  const canSave = Boolean(
    definition?.published_revision &&
    (!targetRequired || draft.scopeId.trim()) &&
    requiredMissing.length === 0
  );

  return (
    <Dialog
      open={open}
      title={editing === "new" ? "Add View assignment" : "Edit View assignment"}
      onClose={onClose}
      closeDisabled={busy}
      className="views-assignment-dialog"
      footer={
        <>
          <Button onClick={onClose} disabled={busy}>Cancel</Button>
          <Button
            variant="primary"
            onClick={onSave}
            disabled={busy || !canSave}
          >
            Save
          </Button>
        </>
      }
    >
      <div className="form-grid two responsive-form-grid">
        <FormField label="Target level">
          <select
            value={draft.scopeType}
            disabled={editing !== "new"}
            onChange={(event) =>
              onChange({
                ...draft,
                scopeType: event.target.value as ViewAssignmentScopeType,
                scopeId: ""
              })
            }
          >
            {scopeType === "system" ? (
              <option value="system">System</option>
            ) : (
              <>
                <option value="tenant">Tenant</option>
                <option value="group">Group</option>
                <option value="user">User</option>
              </>
            )}
          </select>
        </FormField>
        <FormField label="Target ID">
          <input
            value={
              draft.scopeType === "system" || draft.scopeType === "tenant"
                ? ""
                : draft.scopeId
            }
            placeholder={
              targetRequired ? `${draft.scopeType} identifier` : "Current scope"
            }
            disabled={editing !== "new" || !targetRequired}
            onChange={(event) =>
              onChange({ ...draft, scopeId: event.target.value })
            }
          />
        </FormField>
        <FormField label="View">
          <select
            value={draft.definitionId}
            disabled={editing !== "new"}
            onChange={(event) =>
              onChange({ ...draft, definitionId: event.target.value })
            }
          >
            {definitions
              .filter((item) => item.status === "published")
              .map((item) => (
                <option key={item.id} value={item.id}>
                  {item.name} ({item.scope_type})
                </option>
              ))}
          </select>
        </FormField>
        <FormField label="Mode">
          <select
            value={draft.mode}
            onChange={(event) =>
              onChange({
                ...draft,
                mode: event.target.value as ViewAssignmentMode
              })
            }
          >
            <option value="available">Available</option>
            <option value="default">Default</option>
            <option value="required">Required</option>
          </select>
        </FormField>
        <FormField label="Priority">
          <input
            type="number"
            min={-1000}
            max={1000}
            value={draft.priority}
            onChange={(event) =>
              onChange({
                ...draft,
                priority: Number(event.target.value) || 0
              })
            }
          />
        </FormField>
        <div className="views-assignment-toggles">
          <ToggleSwitch
            label="Assignment active"
            checked={draft.active}
            onChange={(active) => onChange({ ...draft, active })}
          />
          <ToggleSwitch
            label="Pin current published revision"
            checked={draft.pinRevision}
            onChange={(pinRevision) => onChange({ ...draft, pinRevision })}
            help="Pinned assignments keep the selected revision. Unpinned assignments follow future published revisions."
          />
        </div>
      </div>
      {draft.mode === "required" && (
        <DismissibleAlert
          tone={requiredMissing.length ? "danger" : "warning"}
          dismissible={false}
        >
          {requiredMissing.length
            ? `This revision cannot be required because it hides: ${requiredMissing.join(", ")}.`
            : "Required Views cannot be left by affected users. The selector and administration escape surfaces remain available."}
        </DismissibleAlert>
      )}
    </Dialog>
  );
}


function SurfaceSelector({
  surfaces,
  selected,
  disabled,
  onChange
}: {
  surfaces: PlatformViewSurface[];
  selected: string[];
  disabled: boolean;
  onChange: (surfaceIds: string[]) => void;
}) {
  const [filter, setFilter] = useState("");
  const byId = useMemo(
    () => new Map(surfaces.map((surface) => [surface.id, surface])),
    [surfaces]
  );
  const childrenByParent = useMemo(() => {
    const result = new Map<string, PlatformViewSurface[]>();
    for (const surface of surfaces) {
      if (!surface.parentId) continue;
      const children = result.get(surface.parentId) ?? [];
      children.push(surface);
      result.set(surface.parentId, children);
    }
    for (const children of result.values()) {
      children.sort(surfaceOrder);
    }
    return result;
  }, [surfaces]);
  const modules = useMemo(
    () => surfaces.filter((surface) => surface.kind === "module").sort(surfaceOrder),
    [surfaces]
  );
  const selectedSet = new Set(selected);
  const requiredIds = new Set(
    surfaces.filter((surface) => surface.required).map((surface) => surface.id)
  );

  function descendants(surfaceId: string): string[] {
    const result: string[] = [];
    const visited = new Set([surfaceId]);
    const pending = [...(childrenByParent.get(surfaceId) ?? [])];
    while (pending.length) {
      const child = pending.shift();
      if (!child || visited.has(child.id)) continue;
      visited.add(child.id);
      result.push(child.id);
      pending.push(...(childrenByParent.get(child.id) ?? []));
    }
    return result;
  }

  function withAncestors(next: Set<string>, surfaceId: string) {
    const visited = new Set([surfaceId]);
    let current = byId.get(surfaceId);
    while (current?.parentId) {
      if (visited.has(current.parentId)) break;
      visited.add(current.parentId);
      next.add(current.parentId);
      current = byId.get(current.parentId);
    }
  }

  function normalizeRequired(next: Set<string>) {
    for (const surfaceId of requiredIds) {
      next.add(surfaceId);
      withAncestors(next, surfaceId);
    }
  }

  function toggle(surfaceId: string, checked: boolean) {
    const next = new Set(selectedSet);
    if (checked) {
      next.add(surfaceId);
      withAncestors(next, surfaceId);
    } else {
      next.delete(surfaceId);
      for (const childId of descendants(surfaceId)) next.delete(childId);
    }
    normalizeRequired(next);
    onChange([...next]);
  }

  const normalizedFilter = filter.trim().toLowerCase();

  return (
    <div className="views-surface-selector">
      <div className="views-surface-filter">
        <input
          value={filter}
          placeholder="Filter modules and surfaces"
          aria-label="Filter View surfaces"
          onChange={(event) => setFilter(event.target.value)}
        />
      </div>
      <div className="views-surface-modules">
        {modules.map((moduleSurface) => {
          const moduleSurfaces = [
            moduleSurface,
            ...descendants(moduleSurface.id)
              .map((id) => byId.get(id))
              .filter((item): item is PlatformViewSurface => Boolean(item))
          ];
          const matches = !normalizedFilter || moduleSurfaces.some((surface) =>
            `${surface.label} ${surface.id} ${surface.description ?? ""}`
              .toLowerCase()
              .includes(normalizedFilter)
          );
          if (!matches) return null;
          const childIds = moduleSurfaces.slice(1).map((surface) => surface.id);
          const selectedChildren = childIds.filter((id) => selectedSet.has(id)).length;
          return (
            <details key={moduleSurface.id} open={Boolean(normalizedFilter)}>
              <summary>
                <SurfaceCheckbox
                  checked={selectedSet.has(moduleSurface.id)}
                  indeterminate={
                    selectedChildren > 0 && selectedChildren < childIds.length
                  }
                  disabled={disabled || requiredIds.has(moduleSurface.id)}
                  label={moduleSurface.label}
                  secondary={`${selectedChildren}/${childIds.length}`}
                  onChange={(checked) => toggle(moduleSurface.id, checked)}
                />
              </summary>
              <div className="views-surface-children">
                {moduleSurfaces.slice(1).map((surface) => (
                  <SurfaceCheckbox
                    key={surface.id}
                    checked={selectedSet.has(surface.id)}
                    disabled={disabled || requiredIds.has(surface.id)}
                    label={surface.label}
                    secondary={`${surface.kind} · ${surface.description ?? surface.id}`}
                    onChange={(checked) => toggle(surface.id, checked)}
                  />
                ))}
              </div>
            </details>
          );
        })}
      </div>
    </div>
  );
}


function SurfaceCheckbox({
  checked,
  indeterminate = false,
  disabled,
  label,
  secondary,
  onChange
}: {
  checked: boolean;
  indeterminate?: boolean;
  disabled: boolean;
  label: string;
  secondary: string;
  onChange: (checked: boolean) => void;
}) {
  const ref = useRef<HTMLInputElement>(null);
  useEffect(() => {
    if (ref.current) ref.current.indeterminate = indeterminate;
  }, [indeterminate]);
  return (
    <label className={disabled ? "views-surface-row disabled" : "views-surface-row"}>
      <input
        ref={ref}
        type="checkbox"
        checked={checked}
        disabled={disabled}
        onChange={(event) => onChange(event.target.checked)}
      />
      <span>
        <strong>{label}</strong>
        <small>{secondary}</small>
      </span>
      {checked && <Check size={15} aria-hidden="true" />}
    </label>
  );
}


function emptyAssignmentDraft(scopeType: ViewScopeType): AssignmentDraft {
  return {
    scopeType: scopeType === "system" ? "system" : "tenant",
    scopeId: "",
    definitionId: "",
    mode: "available",
    priority: 0,
    active: true,
    pinRevision: false
  };
}


function definitionDraftKey(draft: DefinitionDraft): string {
  return JSON.stringify({
    name: draft.name.trim(),
    description: draft.description.trim(),
    surfaces: [...new Set(draft.surfaceIds)].sort()
  });
}


function surfaceSetKey(surfaceIds: string[]): string {
  return [...new Set(surfaceIds)].sort().join("\n");
}


function surfaceOrder(
  left: PlatformViewSurface,
  right: PlatformViewSurface
): number {
  return (
    (left.order ?? 100) - (right.order ?? 100) ||
    left.label.localeCompare(right.label) ||
    left.id.localeCompare(right.id)
  );
}
