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

function emptyState(): MaterialsOverlayState {
  return {
    revision: 1,
    activeMaterialIds: [],
    layouts: {},
    legendLayouts: {},
    focusMaterialId: null,
    zoomTool: null,
  };
}

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

function layoutFor(state: MaterialsOverlayState, materialId: MaterialId): MaterialsOverlayLayout {
  return state.layouts[materialId] ?? { ...DEFAULT_MATERIALS_OVERLAY_LAYOUT };
}

function legendLayoutFor(state: MaterialsOverlayState, materialId: MaterialId): MaterialsOverlayLayout {
  return state.legendLayouts[materialId] ?? defaultLegendLayoutForMaterial();
}

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

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

  clear(): MaterialsOverlayState {
    if (this.state.activeMaterialIds.length === 0 && this.state.zoomTool === null) {
      return this.state;
    }
    this.state = {
      revision: this.state.revision + 1,
      activeMaterialIds: [],
      layouts: {},
      legendLayouts: {},
      focusMaterialId: null,
      zoomTool: null,
    };
    return this.state;
  }

  dispatch(event: MaterialsOverlayEvent): MaterialsOverlayState {
    switch (event.kind) {
      case 'hide':
        return this.clear();
      case 'show': {
        if (this.state.activeMaterialIds.includes(event.materialId)) {
          this.state = {
            ...this.state,
            revision: this.state.revision + 1,
            focusMaterialId: event.materialId,
          };
          return this.state;
        }
        this.state = {
          revision: this.state.revision + 1,
          activeMaterialIds: [...this.state.activeMaterialIds, event.materialId],
          layouts: {
            ...this.state.layouts,
            [event.materialId]: initialLayout(event.rotationDeg),
          },
          legendLayouts: {
            ...this.state.legendLayouts,
            [event.materialId]: defaultLegendLayoutForMaterial(),
          },
          focusMaterialId: event.materialId,
          zoomTool: this.state.zoomTool,
        };
        return this.state;
      }
      case 'toggle': {
        if (this.state.activeMaterialIds.includes(event.materialId)) {
          const activeMaterialIds = this.state.activeMaterialIds.filter((id) => id !== event.materialId);
          const layouts = { ...this.state.layouts };
          const legendLayouts = { ...this.state.legendLayouts };
          delete layouts[event.materialId];
          delete legendLayouts[event.materialId];
          const focusMaterialId =
            this.state.focusMaterialId === event.materialId
              ? (activeMaterialIds[activeMaterialIds.length - 1] ?? null)
              : this.state.focusMaterialId;
          this.state = {
            revision: this.state.revision + 1,
            activeMaterialIds,
            layouts,
            legendLayouts,
            focusMaterialId,
            zoomTool: activeMaterialIds.length === 0 ? null : this.state.zoomTool,
          };
          return this.state;
        }
        this.state = {
          revision: this.state.revision + 1,
          activeMaterialIds: [...this.state.activeMaterialIds, event.materialId],
          layouts: {
            ...this.state.layouts,
            [event.materialId]: initialLayout(event.rotationDeg),
          },
          legendLayouts: {
            ...this.state.legendLayouts,
            [event.materialId]: defaultLegendLayoutForMaterial(),
          },
          focusMaterialId: event.materialId,
          zoomTool: this.state.zoomTool,
        };
        return this.state;
      }
      case 'layout.set': {
        if (!this.state.activeMaterialIds.includes(event.materialId)) return this.state;
        this.state = {
          ...this.state,
          revision: this.state.revision + 1,
          focusMaterialId: event.materialId,
          layouts: {
            ...this.state.layouts,
            [event.materialId]: clampMaterialsLayout({
              ...layoutFor(this.state, event.materialId),
              ...event.layout,
            }),
          },
        };
        return this.state;
      }
      case 'legendLayout.set': {
        if (!this.state.activeMaterialIds.includes(event.materialId)) return this.state;
        this.state = {
          ...this.state,
          revision: this.state.revision + 1,
          focusMaterialId: event.materialId,
          legendLayouts: {
            ...this.state.legendLayouts,
            [event.materialId]: clampMaterialsLayout({
              ...legendLayoutFor(this.state, event.materialId),
              ...event.layout,
              rotationDeg: 0,
            }),
          },
        };
        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.activeMaterialIds.length === 0 || !this.state.zoomTool) return this.state;
        const targetId =
          (event.materialId && this.state.activeMaterialIds.includes(event.materialId)
            ? event.materialId
            : null) ??
          this.state.focusMaterialId ??
          this.state.activeMaterialIds[this.state.activeMaterialIds.length - 1] ??
          null;
        if (!targetId) return this.state;
        const factor = this.state.zoomTool === 'zoomIn' ? 1.25 : 1 / 1.25;
        const layout: MaterialsOverlayLayout = zoomMaterialsLayoutAt(
          layoutFor(this.state, targetId),
          event.nx,
          event.ny,
          factor,
        );
        this.state = {
          ...this.state,
          revision: this.state.revision + 1,
          focusMaterialId: targetId,
          layouts: {
            ...this.state.layouts,
            [targetId]: layout,
          },
        };
        return this.state;
      }
      default:
        return this.state;
    }
  }

  ensureMaterialStillExists(materialIds: ReadonlySet<MaterialId>): MaterialsOverlayState {
    const activeMaterialIds = this.state.activeMaterialIds.filter((id) => materialIds.has(id));
    if (activeMaterialIds.length === this.state.activeMaterialIds.length) return this.state;
    if (activeMaterialIds.length === 0) return this.clear();
    const layouts: Record<string, MaterialsOverlayLayout> = {};
    const legendLayouts: Record<string, MaterialsOverlayLayout> = {};
    for (const id of activeMaterialIds) {
      const layout = this.state.layouts[id];
      if (layout) layouts[id] = layout;
      const legend = this.state.legendLayouts[id];
      if (legend) legendLayouts[id] = legend;
    }
    const focusMaterialId =
      this.state.focusMaterialId && activeMaterialIds.includes(this.state.focusMaterialId)
        ? this.state.focusMaterialId
        : (activeMaterialIds[activeMaterialIds.length - 1] ?? null);
    this.state = {
      revision: this.state.revision + 1,
      activeMaterialIds,
      layouts,
      legendLayouts,
      focusMaterialId,
      zoomTool: this.state.zoomTool,
    };
    return this.state;
  }
}
