/*
 * Copyright (c) 2015-2018, IGN France.
 * Copyright (c) 2018-2026, Giro3D team.
 * SPDX-License-Identifier: MIT
 */

import type { Feature } from 'ol';

import { EventDispatcher } from 'three';

import type CoordinateSystem from '../core/geographic/CoordinateSystem';
import type Extent from '../core/geographic/Extent';

export interface GetFeatureResult {
    /**
     * The resulting features.
     */
    features: Readonly<Feature[]>;
}

export interface GetFeatureRequest {
    /**
     * The extent of the request. The source will make the best effort to return features
     * limited to this extent, but may return more features.
     */
    extent: Extent;
    /**
     * Optional abort signal to cancel the feature request.
     */
    signal?: AbortSignal;
}

export interface FeatureSourceEventMap {
    /**
     * Raised when the content of the feature source have changed.
     */
    updated: unknown;
}

/**
 * Interface for feature sources.
 *
 * > [!note]
 * > To implement a feature source, you can use the {@link FeatureSourceBase} base class for convenience.
 */
export interface FeatureSource extends EventDispatcher<FeatureSourceEventMap> {
    /**
     * Initializes the source. The source is not useable before being initialized.
     */
    initialize(options: {
        /**
         * The coordinate system of features that are generated by this source.
         * The source is responsible for reprojecting features if their original
         * CRS is different from the target.
         */
        targetCoordinateSystem: CoordinateSystem;
    }): Promise<void>;
    /**
     * Gets the features matching the request.
     * @param request - The feature request.
     */
    getFeatures(request: GetFeatureRequest): Promise<GetFeatureResult>;
}

export abstract class FeatureSourceBase
    extends EventDispatcher<FeatureSourceEventMap>
    implements FeatureSource
{
    public abstract readonly type: string;

    protected _targetCoordinateSystem: CoordinateSystem | null = null;
    protected _initialized = false;

    public constructor() {
        super();
    }

    public initialize(options: { targetCoordinateSystem: CoordinateSystem }): Promise<void> {
        this._targetCoordinateSystem = options.targetCoordinateSystem;

        this._initialized = true;

        return Promise.resolve();
    }

    /**
     * Raises an event to reload the source.
     */
    public update(): void {
        this.dispatchEvent({ type: 'updated' });
    }

    protected throwIfNotInitialized(): void {
        if (!this._initialized) {
            throw new Error('this source has not been initialized');
        }
    }

    public abstract getFeatures(request: GetFeatureRequest): Promise<GetFeatureResult>;
}
