import {
  clampNpcsLayout,
  DEFAULT_NPCS_OVERLAY_LAYOUT,
  type NpcId,
  type NpcsOverlayEvent,
  type NpcsOverlayLayout,
  type NpcsOverlayState,
  type NpcsZoomTool,
  zoomNpcsLayoutAt,
} from '../../shared/types';

function emptyState(): NpcsOverlayState {
  return {
    revision: 1,
    activeNpcIds: [],
    layouts: {},
    focusNpcId: null,
    zoomTool: null,
  };
}

function layoutFor(state: NpcsOverlayState, npcId: NpcId): NpcsOverlayLayout {
  return state.layouts[npcId] ?? { ...DEFAULT_NPCS_OVERLAY_LAYOUT };
}

export class NpcsOverlayStore {
  private state: NpcsOverlayState = emptyState();

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

  clear(): NpcsOverlayState {
    if (this.state.activeNpcIds.length === 0 && this.state.zoomTool === null) {
      return this.state;
    }
    this.state = {
      revision: this.state.revision + 1,
      activeNpcIds: [],
      layouts: {},
      focusNpcId: null,
      zoomTool: null,
    };
    return this.state;
  }

  dispatch(event: NpcsOverlayEvent): NpcsOverlayState {
    switch (event.kind) {
      case 'hide':
        return this.clear();
      case 'show': {
        if (this.state.activeNpcIds.includes(event.npcId)) {
          this.state = {
            ...this.state,
            revision: this.state.revision + 1,
            focusNpcId: event.npcId,
          };
          return this.state;
        }
        this.state = {
          revision: this.state.revision + 1,
          activeNpcIds: [...this.state.activeNpcIds, event.npcId],
          layouts: {
            ...this.state.layouts,
            [event.npcId]: { ...DEFAULT_NPCS_OVERLAY_LAYOUT },
          },
          focusNpcId: event.npcId,
          zoomTool: this.state.zoomTool,
        };
        return this.state;
      }
      case 'toggle': {
        if (this.state.activeNpcIds.includes(event.npcId)) {
          const activeNpcIds = this.state.activeNpcIds.filter((id) => id !== event.npcId);
          const layouts = { ...this.state.layouts };
          delete layouts[event.npcId];
          const focusNpcId =
            this.state.focusNpcId === event.npcId
              ? (activeNpcIds[activeNpcIds.length - 1] ?? null)
              : this.state.focusNpcId;
          this.state = {
            revision: this.state.revision + 1,
            activeNpcIds,
            layouts,
            focusNpcId,
            zoomTool: activeNpcIds.length === 0 ? null : this.state.zoomTool,
          };
          return this.state;
        }
        this.state = {
          revision: this.state.revision + 1,
          activeNpcIds: [...this.state.activeNpcIds, event.npcId],
          layouts: {
            ...this.state.layouts,
            [event.npcId]: { ...DEFAULT_NPCS_OVERLAY_LAYOUT },
          },
          focusNpcId: event.npcId,
          zoomTool: this.state.zoomTool,
        };
        return this.state;
      }
      case 'layout.set': {
        if (!this.state.activeNpcIds.includes(event.npcId)) return this.state;
        this.state = {
          ...this.state,
          revision: this.state.revision + 1,
          focusNpcId: event.npcId,
          layouts: {
            ...this.state.layouts,
            [event.npcId]: clampNpcsLayout({
              ...layoutFor(this.state, event.npcId),
              ...event.layout,
            }),
          },
        };
        return this.state;
      }
      case 'zoomTool.set': {
        const tool: NpcsZoomTool = event.tool;
        this.state = {
          ...this.state,
          revision: this.state.revision + 1,
          zoomTool: tool,
        };
        return this.state;
      }
      case 'zoomAt': {
        if (this.state.activeNpcIds.length === 0 || !this.state.zoomTool) return this.state;
        const targetId =
          (event.npcId && this.state.activeNpcIds.includes(event.npcId) ? event.npcId : null) ??
          this.state.focusNpcId ??
          this.state.activeNpcIds[this.state.activeNpcIds.length - 1] ??
          null;
        if (!targetId) return this.state;
        const factor = this.state.zoomTool === 'zoomIn' ? 1.25 : 1 / 1.25;
        const layout: NpcsOverlayLayout = zoomNpcsLayoutAt(
          layoutFor(this.state, targetId),
          event.nx,
          event.ny,
          factor,
        );
        this.state = {
          ...this.state,
          revision: this.state.revision + 1,
          focusNpcId: targetId,
          layouts: {
            ...this.state.layouts,
            [targetId]: layout,
          },
        };
        return this.state;
      }
      default:
        return this.state;
    }
  }

  ensureNpcStillExists(npcIds: ReadonlySet<NpcId>): NpcsOverlayState {
    const activeNpcIds = this.state.activeNpcIds.filter((id) => npcIds.has(id));
    if (activeNpcIds.length === this.state.activeNpcIds.length) return this.state;
    if (activeNpcIds.length === 0) return this.clear();
    const layouts: Record<string, NpcsOverlayLayout> = {};
    for (const id of activeNpcIds) {
      const layout = this.state.layouts[id];
      if (layout) layouts[id] = layout;
    }
    const focusNpcId =
      this.state.focusNpcId && activeNpcIds.includes(this.state.focusNpcId)
        ? this.state.focusNpcId
        : (activeNpcIds[activeNpcIds.length - 1] ?? null);
    this.state = {
      revision: this.state.revision + 1,
      activeNpcIds,
      layouts,
      focusNpcId,
      zoomTool: this.state.zoomTool,
    };
    return this.state;
  }
}
