import type { ControlDef, ZElement } from './types';
import type { Op } from './history';
import { parseKey } from './settings';

/**
 * Role manager (T170). `window.ZADEX.user.accessMode` (PHP `Zadex\Team\Access`):
 *  - `full`: no restriction;
 *  - `content`: only the content controls of existing elements (no add / remove / move, no Style / Advanced tabs,
 *    no site / page settings), nothing at all on `designLocked` elements;
 *  - `none`: never reaches the editor (PHP refuses to open it).
 * The server re-checks every save (`Team\ContentGuard`); these helpers only shape the UI and the store guard.
 */
export type AccessMode = 'none' | 'content' | 'full';

export function normalizeMode(value: unknown): AccessMode {
  return value === 'content' || value === 'none' ? value : 'full';
}

/**
 * A control whose value is content (text, media, links ...): Content tab (the default), no `css` map (content-tab
 * controls that generate CSS, such as alignment, are design), not CSS code, not the column structure.
 * Same rule as `Team\ContentGuard::is_content_control()`.
 */
export function isContentControl(control: ControlDef | undefined | null): boolean {
  if (!control) return false;
  if ((control.tab ?? 'content') !== 'content' || (control.css?.length ?? 0) > 0) return false;
  if (control.type === 'structure' || (control.type === 'code' && control.language === 'css')) return false;
  return true;
}

/** True when a settings key (`id::state@bp`) belongs to a content control of that element type. */
export function isContentKey(key: string, controls: Record<string, ControlDef>): boolean {
  const parts = parseKey(key);
  return Boolean(parts && isContentControl(controls[parts.id]));
}

export interface AccessContext {
  mode: AccessMode;
  get(id: string): ZElement | null;
  controlsFor(type: string): Record<string, ControlDef>;
}

/** Whether a batch of document ops may be committed in this access mode (all or nothing). */
export function opsAllowed(ops: Op[], ctx: AccessContext): boolean {
  if (ctx.mode === 'full') return true;
  if (ctx.mode === 'none') return ops.length === 0;
  return ops.every((op) => {
    if (op.type !== 'settings') return false;
    const el = ctx.get(op.id);
    if (!el || el.designLocked) return false;
    const controls = ctx.controlsFor(el.type);
    const keys = new Set([...Object.keys(op.before), ...Object.keys(op.after)]);
    return [...keys].every((k) => isContentKey(k, controls));
  });
}
