import type OlMap from "ol/Map";
import WebGLVectorTileLayer from "ol/layer/WebGLVectorTile";
import type VectorTileSource from "ol/source/VectorTile";
import type { FlatStyleLike, StyleVariables } from "ol/style/flat";

import {
  buildVersionedFlatStyle,
  sanitizeStyleIdentifier,
  VectorTileStyleSession,
} from "./vectorTileStyleSession";

type FeatureStateValue = string | number | boolean | null;

type ApplyStyleOptions = {
  property: string;
  classCount: number;
  template: FlatStyleLike;
  variables: StyleVariables;
};

type LayerStyleControllerOptions = {
  layer: WebGLVectorTileLayer;
  defaultStyle: FlatStyleLike;
  map?: OlMap;
  stateNamespace?: "primary" | "compare";
};

export class LayerStyleController {
  private readonly layer: WebGLVectorTileLayer;
  private readonly source: VectorTileSource | null;
  private readonly defaultStyle: FlatStyleLike;
  private readonly map?: OlMap;
  private readonly stateNamespace?: "primary" | "compare";
  private session: VectorTileStyleSession | null = null;
  private templateSignature = "";

  constructor(options: LayerStyleControllerOptions) {
    this.layer = options.layer;
    this.source = options.layer.getSource() as VectorTileSource | null;
    this.defaultStyle = options.defaultStyle;
    this.map = options.map;
    this.stateNamespace = options.stateNamespace;
  }

  applyNative(options: ApplyStyleOptions) {
    this.disposeSession();
    const signature = `native:${options.property}:${options.classCount}`;
    this.layer.updateStyleVariables(options.variables);
    if (signature !== this.templateSignature) {
      this.layer.setStyle(options.template);
      this.templateSignature = signature;
    }
  }

  applyRuntime(
    options: ApplyStyleOptions,
    stateById: ReadonlyMap<string, FeatureStateValue>,
  ) {
    if (!this.source) return;
    const signature = `runtime:${options.property}:${options.classCount}`;
    const statePropertyKey = sanitizeStyleIdentifier(
      this.stateNamespace
        ? `${this.stateNamespace}_${options.property}`
        : options.property,
    );
    const buildStyle = (
      statePropertyKey: string,
      versionKey: string,
      version: number,
    ) =>
      buildVersionedFlatStyle(
        options.template,
        this.defaultStyle,
        options.property,
        statePropertyKey,
        versionKey,
        version,
      );

    if (!this.session || this.session.propertyKey !== statePropertyKey) {
      this.disposeSession();
      this.session = new VectorTileStyleSession({
        layer: this.layer,
        source: this.source,
        propertyKey: statePropertyKey,
        defaultStyle: this.defaultStyle,
        buildStyle,
        variables: options.variables,
        map: this.map,
        buffered: Boolean(this.map),
      });
    } else {
      this.session.setBuildStyle(buildStyle);
    }
    this.session.updateStyleVariables(options.variables);
    this.templateSignature = signature;
    return this.session.commit(stateById);
  }

  updateVariables(variables: StyleVariables) {
    if (this.session) {
      this.session.updateStyleVariables(variables);
    } else {
      this.layer.updateStyleVariables(variables);
    }
  }

  hasTemplate() {
    return this.templateSignature.length > 0;
  }

  reset() {
    this.disposeSession();
    this.templateSignature = "";
    this.layer.setStyle(this.defaultStyle);
  }

  dispose() {
    this.disposeSession();
  }

  private disposeSession() {
    this.session?.dispose();
    this.session = null;
  }
}
