import { AssertError, UnreachableCaseError } from '../errors/internal-error';
import { ConfigMinifier } from '../utils/config-minifier';
import { ItemType, JsonValue, ResponsiveMode, Side, SizeUnitEnum } from '../utils/types';
import { deepExtendValue } from '../utils/utils';

/** @public */
export interface ResolvedItemConfig {
    // see ItemConfig for comments
    readonly type: ItemType;
    readonly content: readonly ResolvedItemConfig[];
    readonly size: number;
    readonly sizeUnit: SizeUnitEnum;
    readonly minSize: number | undefined;
    readonly minSizeUnit: SizeUnitEnum;
    // id no longer specifies whether an Item is maximised.  This is now done by HeaderItemConfig.maximised
    readonly id: string;
    readonly isClosable: boolean;
}

/** @public */
export namespace ResolvedItemConfig {
    export const defaults: ResolvedItemConfig = {
        type: ItemType.ground, // not really default but need something
        content: [],
        size: 1,
        sizeUnit: SizeUnitEnum.Fractional,
        minSize: undefined,
        minSizeUnit: SizeUnitEnum.Pixel,
        id: '',
        isClosable: true,
    } as const;

    /** Creates a copy of the original ResolvedItemConfig using an alternative content if specified */
    export function createCopy(original: ResolvedItemConfig, content?: ResolvedItemConfig[]): ResolvedItemConfig {
        switch (original.type) {
            case ItemType.ground:
            case ItemType.row:
            case ItemType.column:
                return ResolvedRowOrColumnItemConfig.createCopy(original as ResolvedRowOrColumnItemConfig,
                    content as ResolvedRowOrColumnItemConfig.ChildItemConfig[]);

            case ItemType.stack:
                return ResolvedStackItemConfig.createCopy(original as ResolvedStackItemConfig, content as ResolvedComponentItemConfig[]);

            case ItemType.component:
                return ResolvedComponentItemConfig.createCopy(original as ResolvedComponentItemConfig);

            default:
                throw new UnreachableCaseError('CICC91354', original.type, 'Invalid Config Item type specified');
        }
    }

    export function createDefault(type: ItemType): ResolvedItemConfig {
        switch (type) {
            case ItemType.ground:
                throw new AssertError('CICCDR91562'); // Get default root from LayoutConfig
            case ItemType.row:
            case ItemType.column:
                return ResolvedRowOrColumnItemConfig.createDefault(type);

            case ItemType.stack:
                return ResolvedStackItemConfig.createDefault();

            case ItemType.component:
                return ResolvedComponentItemConfig.createDefault();

            default:
                throw new UnreachableCaseError('CICCDD91563', type, 'Invalid Config Item type specified');
        }
    }

    export function isComponentItem(itemConfig: ResolvedItemConfig): itemConfig is ResolvedComponentItemConfig {
        return itemConfig.type === ItemType.component;
    }

    export function isStackItem(itemConfig: ResolvedItemConfig): itemConfig is ResolvedStackItemConfig {
        return itemConfig.type === ItemType.stack;
    }

    /** @internal */
    export function isGroundItem(itemConfig: ResolvedItemConfig): itemConfig is ResolvedGroundItemConfig {
        return itemConfig.type === ItemType.ground;
    }
}

// Stack or Component
/** @public */
export interface ResolvedHeaderedItemConfig extends ResolvedItemConfig {
    header: ResolvedHeaderedItemConfig.Header | undefined; // undefined means get header settings from LayoutConfig
    readonly maximised: boolean;
}

/** @public */
export namespace ResolvedHeaderedItemConfig {
    export const defaultMaximised = false;

    export interface Header {
        // undefined means get property value from LayoutConfig
        readonly show: false | Side | undefined;
        readonly popout: false | string | undefined;
        readonly maximise: false | string | undefined;
        readonly close: string | undefined;
        readonly minimise: string | undefined;
        readonly tabDropdown: false | string | undefined;
    }

    export namespace Header {
        export function createCopy(original: Header | undefined, show?: false | Side): Header | undefined {
            if (original === undefined) {
                return undefined;
            } else {
                return {
                    show: show ?? original.show,
                    popout: original.popout,
                    close: original.close,
                    maximise: original.maximise,
                    minimise: original.minimise,
                    tabDropdown: original.tabDropdown,
                }
            }
        }
    }
}

/** @public */
export interface ResolvedStackItemConfig extends ResolvedHeaderedItemConfig {
    readonly type: 'stack';
    readonly content: ResolvedComponentItemConfig[];
    /** The index of the active item in the Stack.  Only undefined if the Stack is empty. */
    readonly activeItemIndex: number | undefined;
}

/** @public */
export namespace ResolvedStackItemConfig {
    export const defaultActiveItemIndex = 0;

    export function createCopy(original: ResolvedStackItemConfig, content?: ResolvedComponentItemConfig[]): ResolvedStackItemConfig {
        const result: ResolvedStackItemConfig = {
            type: original.type,
            content: content !== undefined ? copyContent(content) : copyContent(original.content),
            size: original.size,
            sizeUnit: original.sizeUnit,
            minSize: original.minSize,
            minSizeUnit: original.minSizeUnit,
            id: original.id,
            maximised: original.maximised,
            isClosable: original.isClosable,
            activeItemIndex: original.activeItemIndex,
            header: ResolvedHeaderedItemConfig.Header.createCopy(original.header),
        }
        return result;
    }

    export function copyContent(original: ResolvedComponentItemConfig[]): ResolvedComponentItemConfig[] {
        const count = original.length;
        const result = new Array<ResolvedComponentItemConfig>(count);
        for (let i = 0; i < count; i++) {
            result[i] = ResolvedItemConfig.createCopy(original[i]) as ResolvedComponentItemConfig;
        }
        return result;
    }

    export function createDefault(): ResolvedStackItemConfig {
        const result: ResolvedStackItemConfig = {
            type: ItemType.stack,
            content: [],
            size: ResolvedItemConfig.defaults.size,
            sizeUnit: ResolvedItemConfig.defaults.sizeUnit,
            minSize: ResolvedItemConfig.defaults.minSize,
            minSizeUnit: ResolvedItemConfig.defaults.minSizeUnit,
            id: ResolvedItemConfig.defaults.id,
            maximised: ResolvedHeaderedItemConfig.defaultMaximised,
            isClosable: ResolvedItemConfig.defaults.isClosable,
            activeItemIndex: defaultActiveItemIndex,
            header: undefined,
        }
        return result;
    }
}

/** @public */
export interface ResolvedComponentItemConfig extends ResolvedHeaderedItemConfig {
    // see ComponentItemConfig for comments
    readonly type: 'component';
    readonly content: [];
    readonly title: string;
    readonly reorderEnabled: boolean; // Takes precedence over LayoutConfig.reorderEnabled.
    /**
     * The name of the component as specified in layout.registerComponent. Mandatory if type is 'component'.
     */
    readonly componentType: JsonValue;
    readonly componentState?: JsonValue;
}

/** @public */
export namespace ResolvedComponentItemConfig {
    export const defaultReorderEnabled = true;

    export function resolveComponentTypeName(itemConfig: ResolvedComponentItemConfig): string | undefined {
        const componentType = itemConfig.componentType;
        if (typeof componentType === 'string') {
            return componentType;
        } else {
            return undefined;
        }
    }

    export function createCopy(original: ResolvedComponentItemConfig): ResolvedComponentItemConfig {
        const result: ResolvedComponentItemConfig = {
            type: original.type,
            content: [],
            size: original.size,
            sizeUnit: original.sizeUnit,
            minSize: original.minSize,
            minSizeUnit: original.minSizeUnit,
            id: original.id,
            maximised: original.maximised,
            isClosable: original.isClosable,
            reorderEnabled: original.reorderEnabled,
            title: original.title,
            header: ResolvedHeaderedItemConfig.Header.createCopy(original.header),
            componentType: original.componentType,
            componentState: deepExtendValue(undefined, original.componentState) as JsonValue,
        }
        return result;
    }

    export function createDefault(componentType: JsonValue = '', componentState?: JsonValue, title = ''): ResolvedComponentItemConfig {
        const result: ResolvedComponentItemConfig = {
            type: ItemType.component,
            content: [],
            size: ResolvedItemConfig.defaults.size,
            sizeUnit: ResolvedItemConfig.defaults.sizeUnit,
            minSize: ResolvedItemConfig.defaults.minSize,
            minSizeUnit: ResolvedItemConfig.defaults.minSizeUnit,
            id: ResolvedItemConfig.defaults.id,
            maximised: ResolvedHeaderedItemConfig.defaultMaximised,
            isClosable: ResolvedItemConfig.defaults.isClosable,
            reorderEnabled: ResolvedComponentItemConfig.defaultReorderEnabled,
            title,
            header: undefined,
            componentType,
            componentState,
        }
        return result;
    }

    export function copyComponentType(componentType: JsonValue): JsonValue {
        return deepExtendValue({}, componentType) as JsonValue
    }
}

/** Base for Root or RowOrColumn ItemConfigs
 * @public
 */
export interface ResolvedRowOrColumnItemConfig extends ResolvedItemConfig {
    readonly type: 'row' | 'column';
    /** Note that RowOrColumn ResolvedItemConfig contents, can contain ComponentItem itemConfigs.  However
     * when ContentItems are created, these ComponentItem itemConfigs will create a Stack with a child ComponentItem.
     */
    readonly content: readonly (ResolvedRowOrColumnItemConfig | ResolvedStackItemConfig | ResolvedComponentItemConfig)[];
}

/** @public */
export namespace ResolvedRowOrColumnItemConfig {
    export type ChildItemConfig = ResolvedRowOrColumnItemConfig | ResolvedStackItemConfig | ResolvedComponentItemConfig;

    export function isChildItemConfig(itemConfig: ResolvedItemConfig): itemConfig is ChildItemConfig {
        switch (itemConfig.type) {
            case ItemType.row:
            case ItemType.column:
            case ItemType.stack:
            case ItemType.component:
                return true;
            case ItemType.ground:
                return false;
            default:
                throw new UnreachableCaseError('CROCOSPCICIC13687', itemConfig.type);
        }
    }

    export function createCopy(original: ResolvedRowOrColumnItemConfig, content?: ChildItemConfig[]): ResolvedRowOrColumnItemConfig {
        const result: ResolvedRowOrColumnItemConfig = {
            type: original.type,
            content: content !== undefined ? copyContent(content) : copyContent(original.content),
            size: original.size,
            sizeUnit: original.sizeUnit,
            minSize: original.minSize,
            minSizeUnit: original.minSizeUnit,
            id: original.id,
            isClosable: original.isClosable,
        }
        return result;
    }

    export function copyContent(original: readonly ChildItemConfig[]): ChildItemConfig[] {
        const count = original.length;
        const result = new Array<ChildItemConfig>(count);
        for (let i = 0; i < count; i++) {
            result[i] = ResolvedItemConfig.createCopy(original[i]) as ChildItemConfig;
        }
        return result;
    }

    export function createDefault(type: 'row' | 'column'): ResolvedRowOrColumnItemConfig {
        const result: ResolvedRowOrColumnItemConfig = {
            type,
            content: [],
            size: ResolvedItemConfig.defaults.size,
            sizeUnit: ResolvedItemConfig.defaults.sizeUnit,
            minSize: ResolvedItemConfig.defaults.minSize,
            minSizeUnit: ResolvedItemConfig.defaults.minSizeUnit,
            id: ResolvedItemConfig.defaults.id,
            isClosable: ResolvedItemConfig.defaults.isClosable,
        }
        return result;
    }
}

/**
 * RootItemConfig is the topmost ResolvedItemConfig specified by the user.
 * Note that it does not have a corresponding contentItem.  It specifies the one and only child of the Ground ContentItem
 * Note that RootItemConfig can be an ComponentItem itemConfig.  However when the Ground ContentItem's child is created
 * a ComponentItem itemConfig will create a Stack with a child ComponentItem.
 * @public
*/
export type ResolvedRootItemConfig = ResolvedRowOrColumnItemConfig | ResolvedStackItemConfig | ResolvedComponentItemConfig;

/** @public */
export namespace ResolvedRootItemConfig {
    export function createCopy(config: ResolvedRootItemConfig): ResolvedRootItemConfig {
        return ResolvedItemConfig.createCopy(config) as ResolvedRootItemConfig;
    }

    export function isRootItemConfig(itemConfig: ResolvedItemConfig): itemConfig is ResolvedRootItemConfig {
        switch (itemConfig.type) {
            case ItemType.row:
            case ItemType.column:
            case ItemType.stack:
            case ItemType.component:
                return true;
            case ItemType.ground:
                return false;
            default:
                throw new UnreachableCaseError('CROCOSPCICIC13687', itemConfig.type);
        }
    }
}

/** @internal */
export interface ResolvedGroundItemConfig extends ResolvedItemConfig {
    readonly type: 'ground';
    readonly size: 100,
    readonly sizeUnit: SizeUnitEnum.Percent,
    readonly minSize: 0,
    readonly minSizeUnit: SizeUnitEnum.Pixel,
    readonly id: '',
    readonly isClosable: false,
    readonly title: '',
    readonly reorderEnabled: false,
}

/** @internal */
export namespace ResolvedGroundItemConfig {
    export function create(rootItemConfig: ResolvedRootItemConfig | undefined):ResolvedGroundItemConfig {
        const content = rootItemConfig === undefined ? [] : [rootItemConfig];
        return {
            type: ItemType.ground,
            content,
            size: 100,
            sizeUnit: SizeUnitEnum.Percent,
            minSize: 0,
            minSizeUnit: SizeUnitEnum.Pixel,
            id: '',
            isClosable: false,
            title: '',
            reorderEnabled: false,
        }
    }
}

/** @public */
export interface ResolvedLayoutConfig {
    readonly root: ResolvedRootItemConfig | undefined;
    readonly openPopouts: ResolvedPopoutLayoutConfig[];
    readonly dimensions: ResolvedLayoutConfig.Dimensions;
    readonly settings: ResolvedLayoutConfig.Settings;
    readonly header: ResolvedLayoutConfig.Header;
    readonly resolved: true,
}

/** @public */
export namespace ResolvedLayoutConfig {
    export interface Settings {
        // see Config.Settings for comments
        readonly constrainDragToContainer: boolean;
        readonly reorderEnabled: boolean; // also in ResolvedItemConfig which takes precedence
        readonly popoutWholeStack: boolean;
        readonly blockedPopoutsThrowError: boolean;
        /** @deprecated Will be removed in version 3. */
        readonly closePopoutsOnUnload: boolean;
        readonly responsiveMode: ResponsiveMode;
        readonly tabOverlapAllowance: number;
        readonly reorderOnTabMenuClick: boolean;
        readonly tabControlOffset: number;
        readonly popInOnClose: boolean;
    }

    export namespace Settings {
        export const defaults: ResolvedLayoutConfig.Settings = {
            constrainDragToContainer: true,
            reorderEnabled: true,
            popoutWholeStack: false,
            blockedPopoutsThrowError: true,
            closePopoutsOnUnload: true,
            responsiveMode: ResponsiveMode.none, // was onload
            tabOverlapAllowance: 0,
            reorderOnTabMenuClick: true,
            tabControlOffset: 10,
            popInOnClose: false,
        } as const;

        export function createCopy(original: Settings): Settings {
            return {
                constrainDragToContainer: original.constrainDragToContainer,
                reorderEnabled: original.reorderEnabled,
                popoutWholeStack: original.popoutWholeStack,
                blockedPopoutsThrowError: original.blockedPopoutsThrowError,
                closePopoutsOnUnload: original.closePopoutsOnUnload,
                responsiveMode: original.responsiveMode,
                tabOverlapAllowance: original.tabOverlapAllowance,
                reorderOnTabMenuClick: original.reorderOnTabMenuClick,
                tabControlOffset: original.tabControlOffset,
                popInOnClose: original.popInOnClose,
            }
        }
    }

    export interface Dimensions {
        // see LayoutConfig.Dimensions for comments
        readonly borderWidth: number;
        readonly borderGrabWidth: number,
        readonly defaultMinItemHeight: number;
        readonly defaultMinItemHeightUnit: SizeUnitEnum;
        readonly defaultMinItemWidth: number;
        readonly defaultMinItemWidthUnit: SizeUnitEnum;
        readonly headerHeight: number;
        readonly dragProxyWidth: number;
        readonly dragProxyHeight: number;
    }

    export namespace Dimensions {
        export function createCopy(original: Dimensions): Dimensions {
            return {
                borderWidth: original.borderWidth,
                borderGrabWidth: original.borderGrabWidth,
                defaultMinItemHeight: original.defaultMinItemHeight,
                defaultMinItemHeightUnit: original.defaultMinItemHeightUnit,
                defaultMinItemWidth: original.defaultMinItemWidth,
                defaultMinItemWidthUnit: original.defaultMinItemWidthUnit,
                headerHeight: original.headerHeight,
                dragProxyWidth: original.dragProxyWidth,
                dragProxyHeight: original.dragProxyHeight,
            }
        }

        export const defaults: ResolvedLayoutConfig.Dimensions = {
            borderWidth: 5,
            borderGrabWidth: 5,
            defaultMinItemHeight: 0,
            defaultMinItemHeightUnit: SizeUnitEnum.Pixel,
            defaultMinItemWidth: 10,
            defaultMinItemWidthUnit: SizeUnitEnum.Pixel,
            headerHeight: 20,
            dragProxyWidth: 300,
            dragProxyHeight: 200
        } as const;
    }

    export interface Header {
        readonly show: false | Side;
        readonly popout: false | string;
        readonly dock: string;
        readonly maximise: false | string;
        readonly minimise: string;
        readonly close: false | string;
        readonly tabDropdown: false | string;
    }

    export namespace Header {
        export function createCopy(original: Header): Header {
            return {
                show: original.show,
                popout: original.popout,
                dock: original.dock,
                close: original.close,
                maximise: original.maximise,
                minimise: original.minimise,
                tabDropdown: original.tabDropdown,
            }
        }

        export const defaults: ResolvedLayoutConfig.Header = {
            show: Side.top,
            popout: 'open in new window',
            dock: 'dock',
            maximise: 'maximise',
            minimise: 'minimise',
            close: 'close',
            tabDropdown: 'additional tabs'
        } as const;
    }

    export function isPopout(config: ResolvedLayoutConfig): config is ResolvedPopoutLayoutConfig {
        return 'parentId' in config;
    }

    export function createDefault(): ResolvedLayoutConfig {
        const result: ResolvedLayoutConfig = {
            root: undefined,
            openPopouts: [],
            dimensions: ResolvedLayoutConfig.Dimensions.defaults,
            settings: ResolvedLayoutConfig.Settings.defaults,
            header: ResolvedLayoutConfig.Header.defaults,
            resolved: true,
        }
        return result;
    }

    export function createCopy(config: ResolvedLayoutConfig): ResolvedLayoutConfig {
        if (isPopout(config)) {
            return ResolvedPopoutLayoutConfig.createCopy(config);
        } else {
            const result: ResolvedLayoutConfig = {
                root: config.root === undefined ? undefined : ResolvedRootItemConfig.createCopy(config.root),
                openPopouts: ResolvedLayoutConfig.copyOpenPopouts(config.openPopouts),
                settings: ResolvedLayoutConfig.Settings.createCopy(config.settings),
                dimensions: ResolvedLayoutConfig.Dimensions.createCopy(config.dimensions),
                header: ResolvedLayoutConfig.Header.createCopy(config.header),
                resolved: config.resolved,
            }
            return result;
        }
    }

    export function copyOpenPopouts(original: ResolvedPopoutLayoutConfig[]): ResolvedPopoutLayoutConfig[] {
        const count = original.length;
        const result = new Array<ResolvedPopoutLayoutConfig>(count);
        for (let i = 0; i < count; i++) {
            result[i] = ResolvedPopoutLayoutConfig.createCopy(original[i]);
        }
        return result;
    }

    /**
     * Takes a GoldenLayout configuration object and
     * replaces its keys and values recursively with
     * one letter counterparts
     */
    export function minifyConfig(layoutConfig: ResolvedLayoutConfig): ResolvedLayoutConfig {
        return ConfigMinifier.translateObject(layoutConfig, true) as ResolvedLayoutConfig;
    }

    /**
     * Takes a configuration Object that was previously minified
     * using minifyConfig and returns its original version
     */
    export function unminifyConfig(minifiedConfig: ResolvedLayoutConfig): ResolvedLayoutConfig {
        return ConfigMinifier.translateObject(minifiedConfig, false) as ResolvedLayoutConfig;
    }
}

/** @public */
export interface ResolvedPopoutLayoutConfig extends ResolvedLayoutConfig {
    readonly parentId: string | null;
    readonly indexInParent: number | null;
    readonly window: ResolvedPopoutLayoutConfig.Window;
}

/** @public */
export namespace ResolvedPopoutLayoutConfig {
    export interface Window {
        readonly width: number | null,
        readonly height: number | null,
        readonly left: number | null,
        readonly top: number | null,
    }

    export namespace Window {
        export function createCopy(original: Window): Window {
            return {
                width: original.width,
                height: original.height,
                left: original.left,
                top: original.top,
            }
        }

        export const defaults: ResolvedPopoutLayoutConfig.Window = {
            width: null,
            height: null,
            left: null,
            top: null,
        } as const;
    }

    export function createCopy(original: ResolvedPopoutLayoutConfig): ResolvedPopoutLayoutConfig {
        const result: ResolvedPopoutLayoutConfig = {
            root: original.root === undefined ? undefined : ResolvedRootItemConfig.createCopy(original.root),
            openPopouts: ResolvedLayoutConfig.copyOpenPopouts(original.openPopouts),
            settings: ResolvedLayoutConfig.Settings.createCopy(original.settings),
            dimensions: ResolvedLayoutConfig.Dimensions.createCopy(original.dimensions),
            header: ResolvedLayoutConfig.Header.createCopy(original.header),
            parentId: original.parentId,
            indexInParent: original.indexInParent,
            window: ResolvedPopoutLayoutConfig.Window.createCopy(original.window),
            resolved: original.resolved,
        }
        return result;
    }
}
