// *****************************************************************************
// Copyright (C) 2017 TypeFox and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************

import * as fs from 'fs-extra';
import * as paths from 'path';
import * as semver from 'semver';
import { NpmRegistry, PublishedNodePackage, NodePackage } from './npm-registry';

export interface Extension {
    frontendPreload?: string;
    frontendOnlyPreload?: string;
    frontend?: string;
    frontendOnly?: string;
    frontendElectron?: string;
    secondaryWindow?: string;
    backend?: string;
    backendElectron?: string;
    electronMain?: string;
    preload?: string;
}

export interface ExtensionPackageOptions {
    /**
     * Alias to use in place of the original package's name.
     */
    alias?: string
}

export class ExtensionPackage {

    protected _name: string;

    constructor(
        readonly raw: PublishedNodePackage & Partial<RawExtensionPackage>,
        protected readonly registry: NpmRegistry,
        options: ExtensionPackageOptions = {}
    ) {
        this._name = options.alias ?? raw.name;
    }

    /**
     * The name of the extension's package as defined in "dependencies" (might be aliased)
     */
    get name(): string {
        return this._name;
    }

    get version(): string {
        if (this.raw.installed) {
            return this.raw.installed.version;
        }
        if (this.raw.view) {
            const latestVersion = this.raw.view.latestVersion;
            if (latestVersion) {
                return latestVersion;
            }
        }
        return this.raw.version;
    }

    get description(): string {
        return this.raw.description || '';
    }

    get theiaExtensions(): Extension[] {
        return this.raw.theiaExtensions || [];
    }

    get installed(): boolean {
        return !!this.raw.installed;
    }

    get dependent(): string | undefined {
        if (!this.transitive) {
            return undefined;
        }
        let current = this.parent!;
        let parent = current.parent;
        while (parent !== undefined) {
            current = parent;
            parent = current.parent;
        }
        return current.name;
    }

    get transitive(): boolean {
        return !!this.raw.installed && this.raw.installed.transitive;
    }

    get parent(): ExtensionPackage | undefined {
        if (this.raw.installed) {
            return this.raw.installed.parent;
        }
        return undefined;
    }

    protected async view(): Promise<RawExtensionPackage.ViewState> {
        if (this.raw.view === undefined) {
            const raw = await RawExtensionPackage.view(this.registry, this.name, this.version);
            this.raw.view = raw ? raw.view : new RawExtensionPackage.ViewState(this.registry);
        }
        return this.raw.view!;
    }

    protected readme?: string;
    async getReadme(): Promise<string> {
        if (this.readme === undefined) {
            this.readme = await this.resolveReadme();
        }
        return this.readme;
    }
    protected async resolveReadme(): Promise<string> {
        const raw = await this.view();
        if (raw && raw.readme) {
            return raw.readme;
        }
        if (this.raw.installed) {
            const readmePath = paths.resolve(this.raw.installed.packagePath, '..', 'README.md');
            if (await fs.pathExists(readmePath)) {
                return fs.readFile(readmePath, { encoding: 'utf8' });
            }
            return '';
        }
        return '';
    }

    getAuthor(): string {
        if (this.raw.publisher) {
            return this.raw.publisher.username;
        }
        if (typeof this.raw.author === 'string') {
            return this.raw.author;
        }
        if (this.raw.author && this.raw.author.name) {
            return this.raw.author.name;
        }
        if (!!this.raw.maintainers && this.raw.maintainers.length > 0) {
            return this.raw.maintainers[0].username;
        }
        return '';
    }

}

export interface RawExtensionPackage extends PublishedNodePackage {
    installed?: RawExtensionPackage.InstalledState
    view?: RawExtensionPackage.ViewState
    theiaExtensions: Extension[];
}
export namespace RawExtensionPackage {
    export interface InstalledState {
        version: string;
        packagePath: string;
        transitive: boolean;
        parent?: ExtensionPackage;
    }
    export class ViewState {
        readme?: string;
        tags?: {
            [tag: string]: string
        };
        constructor(
            protected readonly registry: NpmRegistry
        ) { }
        get latestVersion(): string | undefined {
            if (this.tags) {
                if (this.registry.props.next) {
                    const next = this.tags['next'];
                    if (next !== undefined) {
                        return next;
                    }
                }
                const latest = this.tags['latest'];
                if (this.registry.props.next || !semver.prerelease(latest)) {
                    return latest;
                }
                return undefined;
            }
            return undefined;
        }
    }
    export function is(pck: NodePackage | undefined): pck is RawExtensionPackage {
        return PublishedNodePackage.is(pck) && !!pck.theiaExtensions;
    }
    export async function view(registry: NpmRegistry, name: string, version?: string): Promise<RawExtensionPackage | undefined> {
        const result = await registry.view(name).catch(() => undefined);
        if (!result) {
            return undefined;
        }
        const tags = result['dist-tags'];
        const versions = [tags['latest']];
        if (registry.props.next) {
            versions.push(tags['next']);
        }
        if (version) {
            versions.push(tags[version], version);
        }
        for (const current of versions.reverse()) {
            const raw = result.versions[current];
            if (is(raw)) {
                const viewState = new ViewState(registry);
                viewState.readme = result.readme;
                viewState.tags = tags;
                raw.view = viewState;
                return raw;
            }
        }
        return undefined;
    }
}
