import * as _ from 'lodash';
import * as React from 'react';
import styled from 'styled-components';
import * as zeroExInstant from 'zeroExInstant';

import { DocumentTitle } from 'ts/components/document_title';
import { Section } from 'ts/components/newLayout';
import { SiteWrap } from 'ts/components/siteWrap';
import { Heading } from 'ts/components/text';
import { Input as SearchInput } from 'ts/components/ui/search_textfield';
import { addFadeInAnimation } from 'ts/constants/animations';
import { EDITORIAL, FILTERS, ORDERINGS, PROJECTS } from 'ts/pages/explore/explore_content';
import { ExploreSettingsDropdown } from 'ts/pages/explore/explore_dropdown';
import { ExploreGrid } from 'ts/pages/explore/explore_grid';
import { EXPLORE_STATE_DIALOGS, ExploreGridDialogTile } from 'ts/pages/explore/explore_grid_state_tile';
import { ExploreTagButton } from 'ts/pages/explore/explore_tag_button';
import { analytics } from 'ts/utils/analytics';

import {
    ExploreAnalyticAction,
    ExploreFilterMetadata,
    ExploreProject,
    ExploreProjectInstantMetadata,
    ExploreTile,
    ExploreTilesModifiers,
    ExploreTilesOrdering,
    ExploreTilesOrderingMetadata,
    ExploreTilesOrderingType,
    ExploreTileVisibility,
    ExploreTileWidth,
} from 'ts/types';
import { documentConstants } from 'ts/utils/document_meta_constants';

export interface ExploreProps {}

interface ExploreModifierOptions {
    filter?: ExploreFilterMetadata;
    query?: string;
    isEditorialShown?: boolean;
    tilesOrdering?: ExploreTilesOrdering;
}

export class Explore extends React.Component<ExploreProps> {
    public state = {
        isTilesLoading: false,
        tiles: [] as ExploreTile[],
        tilesOrdering: ExploreTilesOrdering.Popular,
        isEditorialShown: true,
        filters: FILTERS,
        query: '',
    };

    private readonly _debouncedChangeSearchResults: (query: string) => void;

    constructor(props: ExploreProps) {
        super(props);
        this._debouncedChangeSearchResults = _.debounce(this._changeSearchResults, 300);
    }

    // tslint:disable-next-line:async-suffix
    public async componentDidMount(): Promise<void> {
        await this._loadEntriesAsync();
        await this._setFilter('all');
    }

    public render(): React.ReactNode {
        return (
            <SiteWrap theme="light">
                <DocumentTitle {...documentConstants.EXPLORE} />
                <ExploreHero query={this.state.query} onSearch={this._setNewQuery} />
                <Section isPadded={false} padding={'0 0 60px 0'} maxWidth={'1150px'}>
                    <ExploreToolBar
                        onFilterClick={this._setFilter}
                        filters={this.state.filters}
                        orderings={_.values(ORDERINGS)}
                        activeOrdering={this.state.tilesOrdering}
                        onOrdering={this._onOrdering}
                    />
                    <ExploreGrid tiles={this._generateTilesFromState()} />
                </Section>
            </SiteWrap>
        );
    }

    // tslint:disable-next-line:no-unused-variable
    private readonly _onEditorial = async (newValue: boolean): Promise<void> => {
        const newTiles = await this._generateTilesWithModifier(this.state.tiles, ExploreTilesModifiers.Editorial, {
            isEditorialShown: newValue,
        });
        this.setState({ isEditorialShown: newValue, tiles: newTiles });
    };

    private readonly _onOrdering = async (newValue: string): Promise<void> => {
        this.setState({ tilesOrdering: newValue });
        const newTiles = await this._generateTilesWithModifier(this.state.tiles, ExploreTilesModifiers.Ordering, {
            tilesOrdering: newValue as ExploreTilesOrdering,
        });
        this.setState({ tilesOrdering: newValue, tiles: newTiles });
    };

    private readonly _launchInstantAsync = (params: ExploreProjectInstantMetadata): void => {
        zeroExInstant.render(params, 'body');
    };

    private readonly _onAnalytics = (metadata: any, action: ExploreAnalyticAction): void => {
        switch (action) {
            case ExploreAnalyticAction.InstantClick:
                analytics.track('Explore - Instant - Clicked', { name: metadata.name });
                break;
            case ExploreAnalyticAction.LinkClick:
                analytics.track('Explore - Link - Clicked', { name: metadata.name });
                break;
            case ExploreAnalyticAction.FilterClick:
                analytics.track('Explore - Filter - Clicked', { filterName: metadata.filterName });
                break;
            case ExploreAnalyticAction.QuerySearched:
                analytics.track('Explore - Query - Searched', { query: metadata.query });
                break;
            default:
                break;
        }
    };

    // tslint:disable-next-line:no-unused-variable
    private _generateEditorialContent(): void {
        this.setState({ tiles: _.concat([], EDITORIAL, this.state.tiles) });
    }

    private readonly _generateTilesFromState = (): ExploreTile[] => {
        if (this.state.isTilesLoading) {
            return [
                {
                    name: 'loading',
                    component: <ExploreGridDialogTile {...EXPLORE_STATE_DIALOGS.LOADING} />,
                    visibility: ExploreTileVisibility.Visible,
                    width: ExploreTileWidth.FullWidth,
                },
            ];
        }
        if (_.isEmpty(this.state.tiles.filter(t => t.visibility !== ExploreTileVisibility.Hidden))) {
            return [
                {
                    name: 'empty',
                    component: <ExploreGridDialogTile {...EXPLORE_STATE_DIALOGS.EMPTY} />,
                    visibility: ExploreTileVisibility.Visible,
                    width: ExploreTileWidth.FullWidth,
                },
            ];
        }
        return this.state.tiles;
    };

    private readonly _setNewQuery = (query: string): void => {
        this.setState({ query });
        this._debouncedChangeSearchResults(query);
    };

    private readonly _changeSearchResults = async (query: string): Promise<void> => {
        this._onAnalytics({ query }, ExploreAnalyticAction.QuerySearched);
        const searchedTiles = await this._generateTilesWithModifier(this.state.tiles, ExploreTilesModifiers.Search, {
            query,
            filter: this.state.filters.find(f => f.active),
        });
        this.setState({ tiles: searchedTiles });
    };

    private readonly _setFilter = async (filterName: string, active: boolean = true): Promise<void> => {
        if (active) {
            this._onAnalytics({ filterName }, ExploreAnalyticAction.FilterClick);
        }
        let updatedFilters: ExploreFilterMetadata[];
        updatedFilters = this.state.filters.map(f => {
            const newFilter = _.assign({}, f);
            newFilter.active = newFilter.name === filterName ? active : false;
            return newFilter;
        });
        // If no filters are enabled, default to all
        if (_.filter(updatedFilters, f => f.active).length === 0) {
            await this._setFilter('all');
        } else {
            const newTiles = await this._generateTilesWithModifier(
                this.state.tiles,
                _.isEmpty(this.state.query) ? ExploreTilesModifiers.Filter : ExploreTilesModifiers.Search,
                {
                    filter: updatedFilters.find(f => f.active),
                    query: this.state.query,
                },
            );
            this.setState({ filters: updatedFilters, tiles: newTiles });
        }
    };

    private readonly _verifyExploreTilesModifierOptions = (
        modifier: ExploreTilesModifiers,
        options: ExploreModifierOptions,
    ): boolean => {
        if (modifier === ExploreTilesModifiers.Ordering) {
            return _.has(options, 'tilesOrdering');
        }
        if (modifier === ExploreTilesModifiers.Editorial) {
            return _.has(options, 'isEditorialShown');
        }
        if (modifier === ExploreTilesModifiers.Search) {
            return _.has(options, 'filter') && _.has(options, 'query');
        }
        if (modifier === ExploreTilesModifiers.Filter) {
            return _.has(options, 'filter');
        }
        return false;
    };

    private readonly _generateTilesWithModifier = async (
        tiles: ExploreTile[],
        modifier: ExploreTilesModifiers,
        options: ExploreModifierOptions,
    ): Promise<ExploreTile[]> => {
        const trimmedQuery = modifier === ExploreTilesModifiers.Search ? options.query.trim().toLowerCase() : '';
        if (!this._verifyExploreTilesModifierOptions(modifier, options)) {
            return tiles;
        }
        if (modifier === ExploreTilesModifiers.Ordering) {
            switch (ORDERINGS[options.tilesOrdering].type) {
                case ExploreTilesOrderingType.HardCodedByName:
                    return _.sortBy(tiles, t => _.indexOf(ORDERINGS[options.tilesOrdering].hardCoded, t.name));
                case ExploreTilesOrderingType.DynamicBySortFunction:
                    return ORDERINGS[options.tilesOrdering].sort(tiles);
                default:
                    return tiles;
            }
        }
        return _.concat([], tiles).map(t => {
            const newTile = _.assign({}, t);
            if (modifier === ExploreTilesModifiers.Filter || modifier === ExploreTilesModifiers.Search) {
                newTile.visibility =
                    (options.filter.name === 'all' && ExploreTileVisibility.Visible) || newTile.visibility;
                if (!(options.filter.name === 'all') && !!newTile.exploreProject) {
                    newTile.visibility =
                        (_.includes(newTile.exploreProject.keywords, options.filter.name) &&
                            ExploreTileVisibility.Visible) ||
                        ExploreTileVisibility.Hidden;
                }
            }
            if (
                !!newTile.exploreProject &&
                modifier === ExploreTilesModifiers.Search &&
                newTile.visibility === ExploreTileVisibility.Visible
            ) {
                newTile.visibility =
                    (_.chain(newTile.exploreProject.label.toLowerCase())
                        .split(' ')
                        .concat([newTile.exploreProject.label.toLowerCase()])
                        .reduce((a: boolean, s: string) => a || _.startsWith(s, trimmedQuery), false)
                        .value() &&
                        ExploreTileVisibility.Visible) ||
                    ExploreTileVisibility.Hidden;
            }
            if (modifier === ExploreTilesModifiers.Editorial) {
                if (_.startsWith(t.name, 'editorial')) {
                    newTile.visibility = options.isEditorialShown
                        ? ExploreTileVisibility.Visible
                        : ExploreTileVisibility.Hidden;
                }
            }
            return newTile;
        });
    };

    // For future versions, the load entries function can be async
    private readonly _loadEntriesAsync = async (): Promise<void> => {
        this.setState({ isEntriesLoading: true });
        const rawProjects = _.values(PROJECTS);
        const tiles = rawProjects.map((e: ExploreProject) => {
            const exploreProject = _.assign({}, e);
            if (!!exploreProject.instant) {
                exploreProject.instant = _.assign({}, exploreProject.instant);
                exploreProject.onInstantClick = this._launchInstantAsync.bind(this, exploreProject.instant);
            }
            exploreProject.onAnalytics = this._onAnalytics.bind(this, exploreProject);
            return {
                name: e.name,
                exploreProject,
                visibility: ExploreTileVisibility.Visible,
                width: ExploreTileWidth.OneThird,
            };
        });
        const orderedTiles = await this._generateTilesWithModifier(tiles, ExploreTilesModifiers.Ordering, {
            tilesOrdering: this.state.tilesOrdering,
        });
        this.setState({ tiles: orderedTiles, isEntriesLoading: false });
    };
}

const ExploreHeroContentWrapper = styled.div`
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 100px 0;
    ${addFadeInAnimation('0.5s')}
    @media (max-width: 36rem) {
        display: block;
        padding: 50px 0;
    }
`;

const ExploreSearchInputWrapper = styled.div`
    width: 22rem;
    @media (max-width: 52rem) {
        width: 16rem;
    }
    @media (max-width: 36rem) {
        margin-top: 10px;
        padding-left: 5px;
        width: 100%;
    }
`;

interface ExploreHeroProps {
    query: string;
    onSearch(query: string): void;
}

const ExploreHero = (props: ExploreHeroProps) => {
    const onChange = (e: any) => {
        props.onSearch(e.target.value);
    };
    return (
        <Section maxWidth={'1150px'} isPadded={false}>
            <ExploreHeroContentWrapper>
                <Heading isNoMargin={true} size="large">
                    Explore 0x
                </Heading>
                <ExploreSearchInputWrapper>
                    <SearchInput value={props.query} onChange={onChange} placeholder="Search..." />
                </ExploreSearchInputWrapper>
            </ExploreHeroContentWrapper>
        </Section>
    );
};

const ExploreToolBarWrapper = styled.div`
    display: flex;
    justify-content: space-between;
    z-index: 1;
    position: relative;
    ${addFadeInAnimation('0.5s', '0.15s')}
    @media (max-width: 36rem) {
        display: block;
    }
`;

const ExploreToolBarContentWrapper = styled.div`
    display: inline-block;
    white-space: nowrap;
    padding-bottom: 0.4rem
    margin-bottom: 1.6rem;
    overflow-x: auto;
    @media (max-width: 64rem) {
       & > * {
            display: none;
       }
    }
    & > * {
        margin: 0 0.3rem;
    }
    & *:first-child {
        margin-left: 0;
    }
    & *:last-child {
        margin-right: 0;
    }
`;

interface ExploreToolBarProps {
    filters: ExploreFilterMetadata[];
    activeOrdering: ExploreTilesOrdering;
    orderings: ExploreTilesOrderingMetadata[];
    editorial?: boolean;
    onOrdering: (newValue: string) => void;
    onEditorial?: (newValue: boolean) => void;
    onFilterClick(filterName: string, active: boolean): void;
}

const ExploreToolBar = (props: ExploreToolBarProps) => {
    return (
        <ExploreToolBarWrapper>
            <ExploreToolBarContentWrapper>
                {!!props.filters &&
                    props.filters.map(f => {
                        const onClick = () => {
                            props.onFilterClick(f.name, !f.active);
                        };
                        return (
                            <ExploreTagButton onClick={onClick} active={f.active} key={f.name}>
                                {f.label}
                            </ExploreTagButton>
                        );
                    })}
            </ExploreToolBarContentWrapper>
            <ExploreSettingsDropdown {...props} />
        </ExploreToolBarWrapper>
    );
};
