import {
  clampMaterialsLayout,
  DEFAULT_MATERIALS_OVERLAY_LAYOUT,
  type MaterialId,
  type MaterialsOverlayEvent,
  type MaterialsOverlayLayout,
  type MaterialsOverlayState,
  type MaterialsZoomTool,
  zoomMaterialsLayoutAt,
} from '../../shared/types';

function emptyState(): MaterialsOverlayState {
  return {
    revision: 1,
    activeMaterialId: null,
    layout: { ...DEFAULT_MATERIALS_OVERLAY_LAYOUT },
    zoomTool: null,
  };
}

function initialLayout(rotationDeg?: number): MaterialsOverlayLayout {
  return clampMaterialsLayout({
    ...DEFAULT_MATERIALS_OVERLAY_LAYOUT,
    rotationDeg: rotationDeg ?? 0,
  });
}

export class MaterialsOverlayStore {
  private state: MaterialsOverlayState = emptyState();

  getState(): MaterialsOverlayState {
    return this.state;
  }

  clear(): MaterialsOverlayState {
    if (this.state.activeMaterialId === null && this.state.zoomTool === null) {
      return this.state;
    }
    this.state = {
      revision: this.state.revision + 1,
      activeMaterialId: null,
      layout: { ...DEFAULT_MATERIALS_OVERLAY_LAYOUT },
      zoomTool: null,
    };
    return this.state;
  }

  dispatch(event: MaterialsOverlayEvent): MaterialsOverlayState {
    switch (event.kind) {
      case 'hide':
        return this.clear();
      case 'show':
        this.state = {
          revision: this.state.revision + 1,
          activeMaterialId: event.materialId,
          layout: initialLayout(event.rotationDeg),
          zoomTool: this.state.zoomTool,
        };
        return this.state;
      case 'toggle': {
        if (this.state.activeMaterialId === event.materialId) {
          return this.clear();
        }
        this.state = {
          revision: this.state.revision + 1,
          activeMaterialId: event.materialId,
          layout: initialLayout(event.rotationDeg),
          zoomTool: this.state.zoomTool,
        };
        return this.state;
      }
      case 'layout.set': {
        if (this.state.activeMaterialId === null) return this.state;
        this.state = {
          ...this.state,
          revision: this.state.revision + 1,
          layout: clampMaterialsLayout({
            ...this.state.layout,
            ...event.layout,
          }),
        };
        return this.state;
      }
      case 'zoomTool.set': {
        const tool: MaterialsZoomTool = event.tool;
        this.state = {
          ...this.state,
          revision: this.state.revision + 1,
          zoomTool: tool,
        };
        return this.state;
      }
      case 'zoomAt': {
        if (this.state.activeMaterialId === null || !this.state.zoomTool) return this.state;
        const factor = this.state.zoomTool === 'zoomIn' ? 1.25 : 1 / 1.25;
        const layout: MaterialsOverlayLayout = zoomMaterialsLayoutAt(
          this.state.layout,
          event.nx,
          event.ny,
          factor,
        );
        this.state = {
          ...this.state,
          revision: this.state.revision + 1,
          layout,
        };
        return this.state;
      }
      default:
        return this.state;
    }
  }

  ensureMaterialStillExists(materialIds: ReadonlySet<MaterialId>): MaterialsOverlayState {
    const active = this.state.activeMaterialId;
    if (active === null || materialIds.has(active)) return this.state;
    return this.clear();
  }
}
