import crypto from 'node:crypto';

import { applyFieldEffectEraserStroke } from '../../shared/fieldEffectEraser';
import type { EffectsEvent, EffectsState, EffectToolState } from '../../shared/types';

function nowMs(): number {
  return Date.now();
}

function defaultTool(): EffectToolState {
  return { tool: 'none', radiusN: 0.08, intensity: 0.6 };
}

/** Сброс выбранного инструмента (при старте сессии). */
export function effectsDefaultTool(): EffectToolState {
  return defaultTool();
}

export class EffectsStore {
  private state: EffectsState = {
    revision: 1,
    serverNowMs: nowMs(),
    tool: defaultTool(),
    instances: [],
  };

  getState(): EffectsState {
    // Всегда обновляем serverNowMs при чтении — это наш "таймкод" для рендереров.
    return { ...this.state, serverNowMs: nowMs() };
  }

  clear(): EffectsState {
    this.state = {
      ...this.state,
      revision: this.state.revision + 1,
      serverNowMs: nowMs(),
      instances: [],
    };
    return this.state;
  }

  dispatch(event: EffectsEvent): EffectsState {
    const s = this.state;
    const next: EffectsState = applyEvent(s, event, (prefix) => this.makeId(prefix));
    this.state = next;
    return next;
  }

  /** Удаляет истёкшие (по lifetime) эффекты, чтобы state не разрастался бесконечно. */
  pruneExpired(): boolean {
    const now = nowMs();
    const before = this.state.instances.length;
    const kept = this.state.instances.filter((i) => {
      // Пятно льда не истекает по таймеру (только «очистить все» или ластик в UI).
      if (i.type === 'ice' || i.type === 'shadow') return true;
      if (
        i.type === 'lightning' ||
        i.type === 'sunbeam' ||
        i.type === 'poisonCloud' ||
        i.type === 'explosion' ||
        i.type === 'darkness'
      ) {
        return now - i.createdAtMs < i.lifetimeMs;
      }
      if (i.type === 'scorch') {
        return now - i.createdAtMs < i.lifetimeMs;
      }
      if (i.type === 'fog' || i.type === 'fire' || i.type === 'rain' || i.type === 'water') {
        if (i.lifetimeMs === null) return true;
        return now - i.createdAtMs < i.lifetimeMs;
      }
      return true;
    });
    if (kept.length === before) return false;
    this.state = {
      ...this.state,
      revision: this.state.revision + 1,
      serverNowMs: now,
      instances: kept,
    };
    return true;
  }

  makeId(prefix: string): string {
    return `${prefix}_${crypto.randomBytes(6).toString('hex')}_${String(nowMs())}`;
  }
}

function applyEvent(
  state: EffectsState,
  event: EffectsEvent,
  makeId: (prefix: string) => string,
): EffectsState {
  const bump = (patch: Omit<EffectsState, 'revision' | 'serverNowMs'>): EffectsState => ({
    ...patch,
    revision: state.revision + 1,
    serverNowMs: nowMs(),
  });
  switch (event.kind) {
    case 'tool.set':
      return bump({ ...state, tool: event.tool });
    case 'instances.clear':
      return bump({ ...state, instances: [] });
    case 'instance.add':
      return bump({ ...state, instances: [...state.instances, event.instance] });
    case 'instance.remove':
      return bump({ ...state, instances: state.instances.filter((i) => i.id !== event.id) });
    case 'field.erase':
      return bump({
        ...state,
        instances: applyFieldEffectEraserStroke(
          state.instances,
          event.points,
          event.radiusN,
          makeId,
          event.types,
          event.hitMode ?? 'inclusive',
        ),
      });
    default: {
      // Exhaustiveness
      // eslint-disable-next-line @typescript-eslint/no-unused-vars
      const _x: never = event;
      return state;
    }
  }
}
