// Per-project scenario_flags client — the single source of truth
// (m/paliad#149 Phase 2 P0, mig 154). Wraps GET/PATCH
// /api/projects/{id}/scenario-flags so any project-bound surface can
// read + write the same flag map.
//
// Shape on the wire:
//
//   GET  → { flags: { "with_ccr": true, "rule:<uuid>": false }, catalog: [...] }
//   PATCH body: { "with_ccr": true, "with_amend": null }
//     - bool      → write the value verbatim
//     - null      → delete the key (priority-driven default returns)
//     - undefined → caller never sends this key; the value is left alone
//
// Cross-surface coherence: every successful PATCH dispatches a
// `scenario-flag-changed` CustomEvent on document so other surfaces
// (Verfahrensablauf strip, Mode B result-view conditional group) can
// re-render without a fresh fetch. Detail carries the merged map so
// listeners can use it directly.

export interface ScenarioFlagCatalogEntry {
  flag_key: string;
  label_de: string;
  label_en: string;
  description?: string;
  hidden_unless_set: boolean;
}

export interface ScenarioFlagsView {
  flags: Record<string, boolean>;
  catalog: ScenarioFlagCatalogEntry[];
}

// PatchDelta represents a partial update. Keys present with `null`
// delete the entry; keys present with a bool overwrite; keys not
// present are left untouched.
export type ScenarioFlagsDelta = Record<string, boolean | null>;

export interface ScenarioFlagChangedDetail {
  projectId: string;
  flags: Record<string, boolean>;
  // The keys that were touched by the PATCH that fired this event.
  // Useful for surfaces that re-render only when *their* flag moved.
  changedKeys: string[];
}

export const SCENARIO_FLAG_CHANGED_EVENT = "scenario-flag-changed";

// fetchScenarioFlags loads the current state and catalog for a project.
// Returns null if the project is invisible to the caller (404 path) or
// the server rejected the request — callers should fall back to local
// defaults in that case rather than surfacing a hard error to the UI.
export async function fetchScenarioFlags(projectId: string): Promise<ScenarioFlagsView | null> {
  if (!projectId) return null;
  try {
    const resp = await fetch(`/api/projects/${encodeURIComponent(projectId)}/scenario-flags`, {
      headers: { Accept: "application/json" },
    });
    if (!resp.ok) {
      if (resp.status === 401 || resp.status === 403 || resp.status === 404) {
        return null;
      }
      console.warn(`scenario-flags GET ${resp.status}`);
      return null;
    }
    return (await resp.json()) as ScenarioFlagsView;
  } catch (e) {
    console.error("scenario-flags GET failed", e);
    return null;
  }
}

// patchScenarioFlags writes a delta. Returns the merged map on success;
// returns null on failure (caller decides whether to roll back UI).
// Dispatches `scenario-flag-changed` on success so peer surfaces can
// re-sync.
export async function patchScenarioFlags(
  projectId: string,
  delta: ScenarioFlagsDelta,
): Promise<ScenarioFlagsView | null> {
  if (!projectId) return null;
  if (Object.keys(delta).length === 0) return null;
  try {
    const resp = await fetch(`/api/projects/${encodeURIComponent(projectId)}/scenario-flags`, {
      method: "PATCH",
      headers: {
        "Content-Type": "application/json",
        Accept: "application/json",
      },
      body: JSON.stringify(delta),
    });
    if (!resp.ok) {
      console.warn(`scenario-flags PATCH ${resp.status}`);
      return null;
    }
    const view = (await resp.json()) as ScenarioFlagsView;
    dispatchScenarioFlagChanged(projectId, view.flags, Object.keys(delta));
    return view;
  } catch (e) {
    console.error("scenario-flags PATCH failed", e);
    return null;
  }
}

function dispatchScenarioFlagChanged(
  projectId: string,
  flags: Record<string, boolean>,
  changedKeys: string[],
): void {
  const detail: ScenarioFlagChangedDetail = { projectId, flags, changedKeys };
  document.dispatchEvent(new CustomEvent(SCENARIO_FLAG_CHANGED_EVENT, { detail }));
}

// onScenarioFlagsChanged subscribes a listener and returns an
// unsubscribe function. Convenient for surfaces wired by lifecycle
// hooks (init / teardown).
export function onScenarioFlagsChanged(
  listener: (detail: ScenarioFlagChangedDetail) => void,
): () => void {
  const handler = (e: Event) => {
    const detail = (e as CustomEvent<ScenarioFlagChangedDetail>).detail;
    if (detail) listener(detail);
  };
  document.addEventListener(SCENARIO_FLAG_CHANGED_EVENT, handler);
  return () => document.removeEventListener(SCENARIO_FLAG_CHANGED_EVENT, handler);
}
