/*
 * Copyright 2020 The Matrix.org Foundation C.I.C.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { Widget } from "./Widget";
import { IWidget } from "..";
import { isValidUrl } from "./validation/url";

export interface IStateEvent {
    event_id: string; // eslint-disable-line camelcase
    room_id: string; // eslint-disable-line camelcase
    type: string;
    sender: string;
    origin_server_ts: number; // eslint-disable-line camelcase
    unsigned?: unknown;
    content: unknown;
    state_key: string; // eslint-disable-line camelcase
}

export interface IAccountDataWidgets {
    [widgetId: string]: {
        type: "m.widget";
        // the state_key is also the widget's ID
        state_key: string; // eslint-disable-line camelcase
        sender: string; // current user's ID
        content: IWidget;
        id?: string; // off-spec, but possible
    };
}

export class WidgetParser {
    private constructor() {
        // private constructor because this is a util class
    }

    /**
     * Parses widgets from the "m.widgets" account data event. This will always
     * return an array, though may be empty if no valid widgets were found.
     * @param {IAccountDataWidgets} content The content of the "m.widgets" account data.
     * @returns {Widget[]} The widgets in account data, or an empty array.
     */
    public static parseAccountData(content: IAccountDataWidgets): Widget[] {
        if (!content) return [];

        const result: Widget[] = [];
        for (const widgetId of Object.keys(content)) {
            const roughWidget = content[widgetId];
            if (!roughWidget) continue;
            if (roughWidget.type !== "m.widget" && roughWidget.type !== "im.vector.modular.widgets") continue;
            if (!roughWidget.sender) continue;

            const probableWidgetId = roughWidget.state_key || roughWidget.id;
            if (probableWidgetId !== widgetId) continue;

            const asStateEvent: IStateEvent = {
                content: roughWidget.content,
                sender: roughWidget.sender,
                type: "m.widget",
                state_key: widgetId,
                event_id: "$example",
                room_id: "!example",
                origin_server_ts: 1,
            };

            const widget = WidgetParser.parseRoomWidget(asStateEvent);
            if (widget) result.push(widget);
        }

        return result;
    }

    /**
     * Parses all the widgets possible in the given array. This will always return
     * an array, though may be empty if no widgets could be parsed.
     * @param {IStateEvent[]} currentState The room state to parse.
     * @returns {Widget[]} The widgets in the state, or an empty array.
     */
    public static parseWidgetsFromRoomState(currentState: IStateEvent[]): Widget[] {
        if (!currentState) return [];
        const result: Widget[] = [];
        for (const state of currentState) {
            const widget = WidgetParser.parseRoomWidget(state);
            if (widget) result.push(widget);
        }
        return result;
    }

    /**
     * Parses a state event into a widget. If the state event does not represent
     * a widget (wrong event type, invalid widget, etc) then null is returned.
     * @param {IStateEvent} stateEvent The state event.
     * @returns {Widget|null} The widget, or null if invalid
     */
    public static parseRoomWidget(stateEvent: IStateEvent): Widget | null {
        if (!stateEvent) return null;

        // TODO: [Legacy] Remove legacy support
        if (stateEvent.type !== "m.widget" && stateEvent.type !== "im.vector.modular.widgets") {
            return null;
        }

        // Dev note: Throughout this function we have null safety to ensure that
        // if the caller did not supply something useful that we don't error. This
        // is done against the requirements of the interface because not everyone
        // will have an interface to validate against.

        const content = stateEvent.content as IWidget || {};

        // Form our best approximation of a widget with the information we have
        const estimatedWidget: IWidget = {
            id: stateEvent.state_key,
            creatorUserId: content['creatorUserId'] || stateEvent.sender,
            name: content['name'],
            type: content['type'],
            url: content['url'],
            waitForIframeLoad: content['waitForIframeLoad'],
            data: content['data'],
        };

        // Finally, process that widget
        return WidgetParser.processEstimatedWidget(estimatedWidget);
    }

    private static processEstimatedWidget(widget: IWidget): Widget | null {
        // Validate that the widget has the best chance of passing as a widget
        if (!widget.id || !widget.creatorUserId || !widget.type) {
            return null;
        }
        if (!isValidUrl(widget.url)) {
            return null;
        }
        // TODO: Validate data for known widget types
        return new Widget(widget);
    }
}
