/**
 * Modal coordinator (docs/architecture/03 → Layer 2).
 *
 * Owns the modal lifecycle: open (close any previous — at most one at a time),
 * init transient state, snapshot for unsaved-change detection, and keep the URL
 * in sync. Framework-light — pure functions over `useAppStore` — so it's unit
 * testable without a DOM. The only coordinator-internal state is the change
 * snapshot, a module-local variable (no component reads it).
 */

import { useAppStore } from '../stores/AppStore';
import { useChartBuilderStore } from '../stores/ChartBuilderStore';
import type { Snippet } from '@core/snippet';
import type { ModalName } from './types';
import { getModalConfig } from './modal-registry';
import { clearModalFromUrl, syncModalToUrl } from './UrlStateSync';

/** Discard-confirmation seam — wired to ConfirmStore at startup (see App). */
let confirmDiscard: (message: string) => Promise<boolean> = () => Promise.resolve(true);
export const setConfirm = (fn: typeof confirmDiscard): void => {
  confirmDiscard = fn;
};

/** The getState() JSON captured at open (or re-baselined), compared on close. */
let stateSnapshot: string | null = null;

function snapshotOf(name: ModalName | null): string | null {
  const get = getModalConfig(name)?.getState;
  if (!get) return null;
  const state = get();
  return state === null ? null : JSON.stringify(state);
}

/** Open `name`, optionally with a sub-target (dataset id, source key). */
export function openModal(name: ModalName, arg?: string): void {
  // TODO: setActiveModal fires the UrlStateSync subscribers before init() has
  // loaded the target, so a navigable modal pushes one hash derived from the
  // *previous* modal state (e.g. a stale `#datasets/dataset-N/build`) before the
  // correct one — a spurious Back step. Swapping the two lines or suppressing
  // sync until init completes would fix it; check init implementations don't
  // assume the modal is already active.
  useAppStore.getState().setActiveModal(name);
  getModalConfig(name)?.init?.(arg);
  stateSnapshot = snapshotOf(name);
  syncModalToUrl(name, arg);
}

/**
 * Open the Chart Builder to edit an existing snippet in place (spec §06 → Open in
 * builder). Hydrates the builder from the snippet (strict spec→config parse + its
 * referenced dataset) *before* showing the modal, deliberately skipping the
 * registry's default `init` — which would re-derive a fresh config and clobber the
 * hydration. A no-op when the snippet isn't losslessly builder-representable or its
 * dataset is gone; the caller gates the entry point on the same two conditions, so
 * this guard only ever fires on a race. Mirrors `openModal`'s bookkeeping minus init
 * (the builder has no `getState`, so there is no editable snapshot to capture).
 */
export function openChartBuilderForEdit(snippet: Snippet): void {
  // An edit session is builder-local and intentionally not URL-serializable: the
  // hydrated builder serializes to `#datasets/dataset-N/build` (the same hash as a
  // *create* flow on that dataset), so a reload / Back / shared link re-runs `init`
  // and reopens a fresh create flow. The published spec is untouched until Save
  // changes, so nothing is lost — only the transient edit context, matching the
  // builder's already-non-persistent configuration (spec §06 → Open in builder).
  if (!useChartBuilderStore.getState().openForEdit(snippet)) return;
  useAppStore.getState().setActiveModal('chartBuilder');
  stateSnapshot = snapshotOf('chartBuilder');
  syncModalToUrl('chartBuilder');
}

/**
 * Re-baseline the change snapshot to the modal's current state. Call after a
 * commit (the form was saved) or when a form view opens, so subsequent
 * unsaved-change detection compares against the right baseline rather than the
 * state at modal-open (a multi-view manager opens its form after open).
 */
export function resnapshot(): void {
  stateSnapshot = snapshotOf(useAppStore.getState().activeModal);
}

/** True when the active modal's editable state differs from its baseline. */
function hasUnsavedChanges(): boolean {
  const name = useAppStore.getState().activeModal;
  if (!name) return false;
  const current = snapshotOf(name);
  if (current === null) return false; // nothing editable open ⇒ nothing to lose
  return current !== stateSnapshot;
}

/** Close the active modal. Prompts on unsaved changes unless `force`. */
export async function closeModal(force = false): Promise<void> {
  const name = useAppStore.getState().activeModal;
  if (!name) return;

  if (!force && hasUnsavedChanges()) {
    const ok = await confirmDiscard('Discard your unsaved changes? This cannot be undone.');
    if (!ok) return;
  }

  clearModalFromUrl(name);
  useAppStore.getState().setActiveModal(null);
  stateSnapshot = null;
}

/** Cmd/Ctrl+K toggle for the Datasets manager (spec §05 → Opening). */
export function toggleDatasets(): void {
  if (useAppStore.getState().activeModal === 'datasets') void closeModal();
  else openModal('datasets');
}
