import type { AsukaView } from '../store/asukaStore';
import type { Asuka3DSceneState, AsukaRenderEngineKind } from './renderEngine';
import type { AsukaPhysicalSize } from './physicalMeasurement';

export const ASUKA_DOCUMENT_FORMAT = 'asuka-document' as const;
export const ASUKA_DOCUMENT_SCHEMA_VERSION = 2 as const;

export type AsukaDocumentEngineMode = AsukaRenderEngineKind | 'mixed';

export interface AsukaDocumentViewportState {
  zoom: number;
  panX: number;
  panY: number;
}

export interface AsukaDocumentDrawingAreaState {
  id: string;
  x: number;
  y: number;
  width: number;
  height: number;
  shape?: string;
  sourceSvg?: string;
  sourcePath?: string;
  physicalSize?: AsukaPhysicalSize;
  [key: string]: unknown;
}

export interface AsukaDocumentSelectionState {
  activeObjectIds: string[];
  activeDrawingAreaId: string | null;
  [key: string]: unknown;
}

export interface AsukaNativeSnapshotsState {
  /** Native Fabric snapshots. Technical cache, not the main business source. */
  fabric?: Record<string, unknown>;
  /** Native Babylon/3D scene snapshots. Technical cache for 3D engines. */
  babylon?: Record<string, unknown>;
  [engine: string]: unknown;
}

export interface AsukaDocumentDrawingAreasState {
  /** Drawing areas for the active view. */
  zones?: AsukaDocumentDrawingAreaState[];
  activeDrawingAreaId?: string | null;
  /** Authoritative per-view drawing/print areas. */
  byView?: Record<string, AsukaDocumentDrawingAreaState[]>;
  activeByView?: Record<string, string | null>;
}

export interface AsukaDocumentState {
  /** Stable document payload marker. */
  format: typeof ASUKA_DOCUMENT_FORMAT;
  /** Stable document schema, independent from app-state schema. */
  schemaVersion: typeof ASUKA_DOCUMENT_SCHEMA_VERSION;
  createdAt: string;
  engineMode: AsukaDocumentEngineMode;
  views: {
    views: AsukaView[];
    activeViewId: string | null;
  };
  /** Engine-neutral design objects by view. This is the business source for 2D content. */
  objectsByView: Record<string, unknown[]>;
  /** Engine-neutral 3D scene state by view. This is the business source for 3D content. */
  scenes3d?: Record<string, Asuka3DSceneState>;
  /** Engine-neutral drawing/print areas. */
  drawingAreas?: AsukaDocumentDrawingAreasState;
  viewportByView?: Record<string, AsukaDocumentViewportState>;
  selection?: AsukaDocumentSelectionState;
  /** Native engine snapshots are technical caches, not the source of truth. */
  nativeSnapshots?: AsukaNativeSnapshotsState;
  /** Embedded element catalog needed by element instances in this document. */
  elements?: unknown[];
  metadata?: Record<string, unknown>;
}

export interface BuildAsukaDocumentInput {
  createdAt?: string;
  views?: AsukaDocumentState['views'];
  objectsByView?: Record<string, unknown[]> | null;
  scenes3d?: Record<string, Asuka3DSceneState> | null;
  drawingAreas?: AsukaDocumentDrawingAreasState | null;
  viewportByView?: Record<string, AsukaDocumentViewportState> | null;
  selection?: AsukaDocumentSelectionState | null;
  nativeSnapshots?: AsukaNativeSnapshotsState | null;
  elements?: unknown[] | null;
  metadata?: Record<string, unknown> | null;
}

function clonePlain<T>(value: T): T {
  if (value == null) return value;
  try { return JSON.parse(JSON.stringify(value)); } catch { return value; }
}

function asRecord<T = unknown>(value: unknown): Record<string, T> {
  return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, T> : {};
}

function normalizeViewEngine(value: unknown): AsukaRenderEngineKind {
  const raw = String(value ?? '').trim().toLowerCase();
  return raw === '3d' ? '3d' : 'fabric';
}

export function resolveAsukaDocumentEngineMode(views: Array<{ engine?: unknown; renderEngine?: unknown; engineKind?: unknown }> | null | undefined): AsukaDocumentEngineMode {
  const kinds = new Set<AsukaRenderEngineKind>();
  for (const view of Array.isArray(views) ? views : []) {
    kinds.add(normalizeViewEngine((view as any)?.engine ?? (view as any)?.renderEngine ?? (view as any)?.engineKind));
  }
  if (kinds.size > 1) return 'mixed';
  return kinds.has('3d') ? '3d' : 'fabric';
}

export function isAsukaDocumentState(value: unknown): value is AsukaDocumentState {
  return !!value
    && typeof value === 'object'
    && (value as any).format === ASUKA_DOCUMENT_FORMAT
    && (value as any).schemaVersion === ASUKA_DOCUMENT_SCHEMA_VERSION;
}

export function buildAsukaDocument(input: BuildAsukaDocumentInput = {}): AsukaDocumentState {
  const viewsPayload = input.views && typeof input.views === 'object'
    ? input.views
    : { views: [], activeViewId: null };
  const views = Array.isArray(viewsPayload.views) ? clonePlain(viewsPayload.views) : [];
  const activeViewId = String(viewsPayload.activeViewId ?? '').trim() || (views[0]?.id ? String(views[0].id) : null);
  const scenes3d = clonePlain(asRecord<Asuka3DSceneState>(input.scenes3d));
  const nativeSnapshots = clonePlain(asRecord(input.nativeSnapshots)) as AsukaNativeSnapshotsState;
  const babylonSnapshots = asRecord<Asuka3DSceneState>((nativeSnapshots as any).babylon);
  const mergedScenes3d = Object.keys(scenes3d).length ? scenes3d : clonePlain(babylonSnapshots);

  const document: AsukaDocumentState = {
    format: ASUKA_DOCUMENT_FORMAT,
    schemaVersion: ASUKA_DOCUMENT_SCHEMA_VERSION,
    createdAt: input.createdAt ?? new Date().toISOString(),
    engineMode: resolveAsukaDocumentEngineMode(views),
    views: { views, activeViewId },
    objectsByView: clonePlain(asRecord<unknown[]>(input.objectsByView)),
  };

  if (Object.keys(mergedScenes3d).length) document.scenes3d = mergedScenes3d;
  if (input.drawingAreas && typeof input.drawingAreas === 'object') document.drawingAreas = clonePlain(input.drawingAreas);
  if (input.viewportByView && typeof input.viewportByView === 'object') document.viewportByView = clonePlain(input.viewportByView);
  if (input.selection && typeof input.selection === 'object') document.selection = clonePlain(input.selection);
  if (Object.keys(nativeSnapshots).length) document.nativeSnapshots = nativeSnapshots;
  if (Array.isArray(input.elements)) document.elements = clonePlain(input.elements);
  if (input.metadata && typeof input.metadata === 'object') document.metadata = clonePlain(input.metadata);

  return document;
}

export function materializeStateDocumentFromAsukaDocument(stateDocument: any): any {
  const source = stateDocument && typeof stateDocument === 'object' ? stateDocument : {};
  const neutral = isAsukaDocumentState(source.asukaDocument) ? source.asukaDocument : null;
  if (!neutral) return source;

  const out: any = { ...source };
  if (!out.views && neutral.views) out.views = clonePlain(neutral.views);
  if (!out.objects && neutral.objectsByView) out.objects = { byView: clonePlain(neutral.objectsByView), source: 'asuka-document' };
  if (!out.scenes3d) out.scenes3d = clonePlain(neutral.scenes3d ?? asRecord((neutral.nativeSnapshots as any)?.babylon));
  if (!out.snapshots && neutral.nativeSnapshots?.fabric) out.snapshots = { snapshotsByView: clonePlain(neutral.nativeSnapshots.fabric) };
  if (!out.drawingAreas && neutral.drawingAreas) out.drawingAreas = clonePlain(neutral.drawingAreas);
  if (!out.viewportByView && neutral.viewportByView) out.viewportByView = clonePlain(neutral.viewportByView);
  if (!out.selection && neutral.selection) out.selection = clonePlain(neutral.selection);
  if (!out.elements && Array.isArray(neutral.elements)) out.elements = clonePlain(neutral.elements);
  return out;
}

export function shouldExpandScopeForAsukaDocument(document: any): boolean {
  return isAsukaDocumentState(document?.asukaDocument);
}
