export type AsukaPlacementPhase =
  | 'active'
  | 'valid'
  | 'constrained'
  | 'blocked'
  | 'committed'
  | 'cancelled';

export interface AsukaPlacementSessionStatus<
  TTransform extends Record<string, unknown> = Record<string, unknown>,
> {
  sessionId: string;
  engine: 'fabric' | '3d';
  objectId: string;
  printAreaId: string | null;
  source: string;
  phase: AsukaPlacementPhase;
  valid: boolean;
  constrained: boolean;
  canCommit: boolean;
  reason: string | null;
  initialTransform: TTransform | null;
  candidateTransform: TTransform | null;
  resolvedTransform: TTransform | null;
  lastValidTransform: TTransform | null;
  metadata: Record<string, unknown>;
  startedAt: number;
  updatedAt: number;
}

export interface AsukaPlacementSessionBeginInput<
  TTransform extends Record<string, unknown> = Record<string, unknown>,
> {
  objectId: string;
  printAreaId?: string | null;
  transform?: TTransform | null;
  source?: string;
  metadata?: Record<string, unknown>;
}

export interface AsukaPlacementSessionUpdateInput<
  TTransform extends Record<string, unknown> = Record<string, unknown>,
> {
  transform?: TTransform | null;
  printAreaId?: string | null;
  valid: boolean;
  constrained?: boolean;
  remember?: boolean;
  reason?: string | null;
  metadata?: Record<string, unknown>;
}

export interface AsukaPlacementSessionController<
  TTransform extends Record<string, unknown> = Record<string, unknown>,
> {
  begin(input: AsukaPlacementSessionBeginInput<TTransform>): AsukaPlacementSessionStatus<TTransform>;
  update(input: AsukaPlacementSessionUpdateInput<TTransform>): AsukaPlacementSessionStatus<TTransform>;
  commit(metadata?: Record<string, unknown>): AsukaPlacementSessionStatus<TTransform> | null;
  cancel(metadata?: Record<string, unknown>): AsukaPlacementSessionStatus<TTransform> | null;
  getStatus(): AsukaPlacementSessionStatus<TTransform> | null;
  clear(): void;
}

let placementSessionSequence = 0;

const cloneValue = <T,>(value: T): T => {
  if (value == null || typeof value !== 'object') return value;
  try {
    if (typeof structuredClone === 'function') return structuredClone(value);
  } catch {}
  try {
    return JSON.parse(JSON.stringify(value)) as T;
  } catch {
    return { ...(value as Record<string, unknown>) } as T;
  }
};

const normalizeId = (value: unknown): string => String(value ?? '').trim();

export function createAsukaPlacementSessionController<
  TTransform extends Record<string, unknown> = Record<string, unknown>,
>(engine: 'fabric' | '3d'): AsukaPlacementSessionController<TTransform> {
  let status: AsukaPlacementSessionStatus<TTransform> | null = null;

  const snapshot = (): AsukaPlacementSessionStatus<TTransform> | null => (
    status ? cloneValue(status) : null
  );

  return {
    begin(input) {
      const objectId = normalizeId(input.objectId);
      if (!objectId) throw new Error('A placement session requires an objectId.');
      const now = Date.now();
      const initialTransform = cloneValue(input.transform ?? null);
      placementSessionSequence += 1;
      status = {
        sessionId: `placement_${now.toString(36)}_${placementSessionSequence.toString(36)}`,
        engine,
        objectId,
        printAreaId: normalizeId(input.printAreaId) || null,
        source: normalizeId(input.source) || 'interaction',
        phase: 'active',
        valid: true,
        constrained: false,
        canCommit: true,
        reason: null,
        initialTransform,
        candidateTransform: initialTransform,
        resolvedTransform: initialTransform,
        lastValidTransform: initialTransform,
        metadata: cloneValue(input.metadata ?? {}),
        startedAt: now,
        updatedAt: now,
      };
      return snapshot()!;
    },

    update(input) {
      if (!status || status.phase === 'committed' || status.phase === 'cancelled') {
        throw new Error('No active placement session.');
      }
      const candidateTransform = input.transform === undefined
        ? status.candidateTransform
        : cloneValue(input.transform);
      const valid = input.valid === true;
      const constrained = input.constrained === true;
      const remember = input.remember ?? valid;
      const lastValidTransform = valid && remember && candidateTransform
        ? candidateTransform
        : status.lastValidTransform;

      status = {
        ...status,
        printAreaId: input.printAreaId === undefined
          ? status.printAreaId
          : (normalizeId(input.printAreaId) || null),
        phase: valid ? (constrained ? 'constrained' : 'valid') : 'blocked',
        valid,
        constrained,
        canCommit: Boolean(valid ? candidateTransform : lastValidTransform),
        reason: normalizeId(input.reason) || null,
        candidateTransform,
        resolvedTransform: valid ? candidateTransform : lastValidTransform,
        lastValidTransform,
        metadata: {
          ...status.metadata,
          ...cloneValue(input.metadata ?? {}),
        },
        updatedAt: Date.now(),
      };
      return snapshot()!;
    },

    commit(metadata) {
      if (!status) return null;
      const resolvedTransform = status.valid
        ? status.candidateTransform
        : status.lastValidTransform;
      status = {
        ...status,
        phase: 'committed',
        valid: Boolean(resolvedTransform),
        canCommit: Boolean(resolvedTransform),
        resolvedTransform,
        metadata: {
          ...status.metadata,
          ...cloneValue(metadata ?? {}),
        },
        updatedAt: Date.now(),
      };
      return snapshot();
    },

    cancel(metadata) {
      if (!status) return null;
      status = {
        ...status,
        phase: 'cancelled',
        valid: Boolean(status.initialTransform),
        canCommit: false,
        resolvedTransform: status.initialTransform,
        metadata: {
          ...status.metadata,
          ...cloneValue(metadata ?? {}),
        },
        updatedAt: Date.now(),
      };
      return snapshot();
    },

    getStatus: snapshot,

    clear() {
      status = null;
    },
  };
}
