import {ProjectionDefinition, type ProjectionSpecification} from '@maplibre/maplibre-gl-style-spec';
import {type PossiblyEvaluated, Transitionable, type Transitioning, type TransitionParameters} from '../../style/properties.ts';
import {getProperties, type ProjectionProps, type ProjectionPropsPossiblyEvaluated} from '../../style/projection_properties.g.ts';
import {Evented} from '../../util/evented.ts';
import {EvaluationParameters} from '../../style/evaluation_parameters.ts';
import {MercatorProjection} from './mercator_projection.ts';
import {VerticalPerspectiveProjection} from './vertical_perspective_projection.ts';
import {type Projection, type TileMeshUsage} from './projection.ts';
import {type PreparedShader} from '../../shaders/shaders.ts';
import {type SubdivisionGranularitySetting} from '../../render/subdivision_granularity_settings.ts';
import {type Context} from '../../webgl/context.ts';
import {type CanonicalTileID} from '../../tile/tile_id.ts';
import {type Mesh} from '../../render/mesh.ts';

export class GlobeProjection extends Evented implements Projection {
    properties: PossiblyEvaluated<ProjectionProps, ProjectionPropsPossiblyEvaluated>;

    _transitionable: Transitionable<ProjectionProps>;
    _transitioning: Transitioning<ProjectionProps>;
    _mercatorProjection: MercatorProjection;
    _verticalPerspectiveProjection: VerticalPerspectiveProjection;

    constructor(projection: ProjectionSpecification | undefined, globalState: Record<string, any>) {
        super();
        this._transitionable = new Transitionable(getProperties(), 'projection', globalState);
        this.setProjection(projection);
        this._transitioning = this._transitionable.untransitioned();
        this.recalculate(new EvaluationParameters(0));
        this._mercatorProjection = new MercatorProjection();
        this._verticalPerspectiveProjection = new VerticalPerspectiveProjection();
    }

    public get transitionState(): number {
        const currentProjectionSpecValue = this.properties.get('type');
        if (typeof currentProjectionSpecValue === 'string' && currentProjectionSpecValue === 'mercator') {
            return 0;
        }
        if (typeof currentProjectionSpecValue === 'string' && currentProjectionSpecValue === 'vertical-perspective') {
            return 1;
        }
        if (currentProjectionSpecValue instanceof ProjectionDefinition) {
            if (currentProjectionSpecValue.from === currentProjectionSpecValue.to) {
                return currentProjectionSpecValue.from === 'mercator' ? 0 : 1;
            }
            if (currentProjectionSpecValue.from === 'vertical-perspective' && currentProjectionSpecValue.to === 'mercator') {
                return 1 - currentProjectionSpecValue.transition;
            }
            if (currentProjectionSpecValue.from === 'mercator' && currentProjectionSpecValue.to === 'vertical-perspective') {
                return currentProjectionSpecValue.transition;
            }
        };
        return 1;
    }

    get useGlobeRendering(): boolean {
        return this.transitionState > 0;
    }

    private get currentProjection(): Projection {
        return this.useGlobeRendering ? this._verticalPerspectiveProjection : this._mercatorProjection;
    }

    get name(): ProjectionSpecification['type'] {
        return 'globe';
    }

    get useSubdivision(): boolean {
        return this.currentProjection.useSubdivision;
    }

    get shaderVariantName(): string {
        return this.currentProjection.shaderVariantName;
    }

    get shaderDefine(): string {
        return this.currentProjection.shaderDefine;
    }

    get shaderPreludeCode(): PreparedShader {
        return this.currentProjection.shaderPreludeCode;
    }

    get vertexShaderPreludeCode(): string {
        return this.currentProjection.vertexShaderPreludeCode;
    }

    get subdivisionGranularity(): SubdivisionGranularitySetting {
        return this.currentProjection.subdivisionGranularity;
    }

    get useGlobeControls(): boolean {
        return this.transitionState > 0;
    }

    public destroy(): void {
        this._mercatorProjection.destroy();
        this._verticalPerspectiveProjection.destroy();
    }

    public getMeshFromTileID(context: Context, _tileID: CanonicalTileID, _hasBorder: boolean, _allowPoles: boolean, _usage: TileMeshUsage): Mesh {
        return this.currentProjection.getMeshFromTileID(context, _tileID, _hasBorder, _allowPoles, _usage);
    }

    setProjection(projection?: ProjectionSpecification): void {
        this._transitionable.setValue('type', projection?.type || 'mercator');
    }

    updateTransitions(parameters: TransitionParameters): void {
        this._transitioning = this._transitionable.transitioned(parameters, this._transitioning);
    }

    hasTransition(): boolean {
        return this._transitioning.hasTransition() || this.currentProjection.hasTransition();
    }

    recalculate(parameters: EvaluationParameters): void {
        this.properties = this._transitioning.possiblyEvaluate(parameters);
    }
}
