import React = require('react');
import { action, computed, observable } from 'mobx';
import { observer } from 'mobx-react';
import { extname } from 'path';
import DatePicker from 'react-datepicker';
import { DateField } from '../../../../fields/DateField';
import { Doc, DocListCast, Field } from '../../../../fields/Doc';
import { RichTextField } from '../../../../fields/RichTextField';
import { BoolCast, Cast, DateCast, FieldValue } from '../../../../fields/Types';
import { ImageField } from '../../../../fields/URLField';
import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero, Utils } from '../../../../Utils';
import { FInfo } from '../../../documents/Documents';
import { DocFocusOrOpen } from '../../../util/DocumentManager';
import { dropActionType } from '../../../util/DragManager';
import { Transform } from '../../../util/Transform';
import { undoBatch } from '../../../util/UndoManager';
import { EditableView } from '../../EditableView';
import { Colors } from '../../global/globalEnums';
import { OpenWhere } from '../../nodes/DocumentView';
import { FieldView, FieldViewProps } from '../../nodes/FieldView';
import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
import { KeyValueBox } from '../../nodes/KeyValueBox';
import { DefaultStyleProvider } from '../../StyleProvider';
import { ColumnType, FInfotoColType } from './CollectionSchemaView';
import './CollectionSchemaView.scss';

export interface SchemaTableCellProps {
    Document: Doc;
    col: number;
    deselectCell: () => void;
    selectCell: (doc: Doc, col: number) => void;
    selectedCell: () => [Doc, number] | undefined;
    fieldKey: string;
    maxWidth?: () => number;
    columnWidth: () => number;
    rowHeight: () => number;
    padding?: number; // default is 5 -- see scss
    isRowActive: () => boolean | undefined;
    getFinfo: (fieldKey: string) => FInfo | undefined;
    setColumnValues: (field: string, value: string) => boolean;
    oneLine?: boolean; // whether all input should fit on one line vs allowing textare multiline inputs
    allowCRs?: boolean; // allow carriage returns in text input (othewrise CR ends the edit)
    finishEdit?: () => void; // notify container that edit is over (eg. to hide view in DashFieldView)
}

@observer
export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
    static addFieldDoc = (doc: Doc, where: OpenWhere) => {
        DocFocusOrOpen(doc);
        return true;
    };
    public static renderProps(props: SchemaTableCellProps) {
        const { Document, fieldKey, getFinfo, columnWidth, isRowActive } = props;
        let protoCount = 0;
        let doc: Doc | undefined = Document;
        while (doc) {
            if (Object.keys(doc).includes(fieldKey.replace(/^_/, ''))) {
                break;
            }
            protoCount++;
            doc = doc.proto;
        }
        const parenCount = Math.max(0, protoCount - 1);
        const color = protoCount === 0 || (fieldKey.startsWith('_') && Document[fieldKey] === undefined) ? 'black' : 'blue';
        const textDecoration = color !== 'black' && parenCount ? 'underline' : '';
        const fieldProps: FieldViewProps = {
            docFilters: returnEmptyFilter,
            docRangeFilters: returnEmptyFilter,
            searchFilterDocs: returnEmptyDoclist,
            styleProvider: DefaultStyleProvider,
            docViewPath: returnEmptyDoclist,
            rootSelected: returnFalse,
            isSelected: returnFalse,
            setHeight: returnFalse,
            select: emptyFunction,
            dropAction: 'alias' as dropActionType,
            bringToFront: emptyFunction,
            renderDepth: 1,
            isContentActive: returnFalse,
            whenChildContentsActiveChanged: emptyFunction,
            ScreenToLocalTransform: Transform.Identity,
            focus: emptyFunction,
            addDocTab: SchemaTableCell.addFieldDoc,
            pinToPres: returnZero,
            Document,
            fieldKey,
            PanelWidth: columnWidth,
            PanelHeight: props.rowHeight,
        };
        const readOnly = getFinfo(fieldKey)?.readOnly ?? false;
        const cursor = !readOnly ? 'text' : 'default';
        const pointerEvents: 'all' | 'none' = !readOnly && isRowActive() ? 'all' : 'none';
        return { color, textDecoration, fieldProps, cursor, pointerEvents };
    }

    @computed get selected() {
        const selected: [Doc, number] | undefined = this.props.selectedCell();
        return this.props.isRowActive() && selected?.[0] === this.props.Document && selected[1] === this.props.col;
    }

    @computed get defaultCellContent() {
        const { color, textDecoration, fieldProps } = SchemaTableCell.renderProps(this.props);

        return (
            <div
                className="schemacell-edit-wrapper"
                style={{
                    color,
                    textDecoration,
                    width: '100%',
                }}>
                <EditableView
                    oneLine={this.props.oneLine}
                    allowCRs={this.props.allowCRs}
                    contents={<FieldView {...fieldProps} />}
                    editing={this.selected ? undefined : false}
                    GetValue={() => Field.toKeyValueString(this.props.Document, this.props.fieldKey)}
                    SetValue={undoBatch((value: string, shiftDown?: boolean, enterKey?: boolean) => {
                        if (shiftDown && enterKey) {
                            this.props.setColumnValues(this.props.fieldKey.replace(/^_/, ''), value);
                        }
                        const ret = KeyValueBox.SetField(this.props.Document, this.props.fieldKey.replace(/^_/, ''), value);
                        this.props.finishEdit?.();
                        return ret;
                    })}
                />
            </div>
        );
    }

    get getCellType() {
        const cellValue = this.props.Document[this.props.fieldKey];
        if (cellValue instanceof ImageField) return ColumnType.Image;
        if (cellValue instanceof DateField) return ColumnType.Date;
        if (cellValue instanceof RichTextField) return ColumnType.RTF;
        if (typeof cellValue === 'number') return ColumnType.Any;
        if (typeof cellValue === 'string') return ColumnType.Any;
        if (typeof cellValue === 'boolean') return ColumnType.Boolean;

        const columnTypeStr = this.props.getFinfo(this.props.fieldKey)?.fieldType;
        if (columnTypeStr && columnTypeStr in FInfotoColType) {
            return FInfotoColType[columnTypeStr];
        }

        return ColumnType.Any;
    }

    get content() {
        const cellType: ColumnType = this.getCellType;
        // prettier-ignore
        switch (cellType) {
            case ColumnType.Image: return <SchemaImageCell {...this.props} />;
            case ColumnType.Boolean: return <SchemaBoolCell {...this.props} />;
            case ColumnType.RTF: return <SchemaRTFCell {...this.props} />;
            case ColumnType.Date: // return <SchemaDateCell {...this.props} />;
            default:  return this.defaultCellContent;
        }
    }

    render() {
        return (
            <div
                className="schema-table-cell"
                onPointerDown={action(e => !this.selected && this.props.selectCell(this.props.Document, this.props.col))}
                style={{ padding: this.props.padding, maxWidth: this.props.maxWidth?.(), width: this.props.columnWidth() || undefined, border: this.selected ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined }}>
                {this.content}
            </div>
        );
    }
}

// mj: most of this is adapted from old schema code so I'm not sure what it does tbh
@observer
export class SchemaImageCell extends React.Component<SchemaTableCellProps> {
    @observable _previewRef: HTMLImageElement | undefined;

    choosePath(url: URL) {
        if (url.protocol === 'data') return url.href; // if the url ises the data protocol, just return the href
        if (url.href.indexOf(window.location.origin) === -1) return Utils.CorsProxy(url.href); // otherwise, put it through the cors proxy erver
        if (!/\.(png|jpg|jpeg|gif|webp)$/.test(url.href.toLowerCase())) return url.href; //Why is this here — good question

        const ext = extname(url.href);
        return url.href.replace(ext, '_s' + ext);
    }

    get url() {
        const field = Cast(this.props.Document[this.props.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
        const alts = DocListCast(this.props.Document[this.props.fieldKey + '-alternates']); // retrieve alternate documents that may be rendered as alternate images
        const altpaths = alts
            .map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url)
            .filter(url => url)
            .map(url => this.choosePath(url)); // access the primary layout data of the alternate documents
        const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths;
        // If there is a path, follow it; otherwise, follow a link to a default image icon
        const url = paths.length ? paths : [Utils.CorsProxy('http://www.cs.brown.edu/~bcz/noImage.png')];
        return url[0];
    }

    @action
    showHoverPreview = (e: React.PointerEvent) => {
        this._previewRef = document.createElement('img');
        document.body.appendChild(this._previewRef);
        const ext = extname(this.url);
        this._previewRef.src = this.url.replace('_s' + ext, '_m' + ext);
        this._previewRef.style.position = 'absolute';
        this._previewRef.style.left = e.clientX + 10 + 'px';
        this._previewRef.style.top = e.clientY + 10 + 'px';
        this._previewRef.style.zIndex = '1000';
    };

    @action
    moveHoverPreview = (e: React.PointerEvent) => {
        if (!this._previewRef) return;
        this._previewRef.style.left = e.clientX + 10 + 'px';
        this._previewRef.style.top = e.clientY + 10 + 'px';
    };

    @action
    removeHoverPreview = (e: React.PointerEvent) => {
        if (!this._previewRef) return;
        document.body.removeChild(this._previewRef);
    };

    render() {
        const aspect = Doc.NativeAspect(this.props.Document); // aspect ratio
        // let width = Math.max(75, this.props.columnWidth); // get a with that is no smaller than 75px
        // const height = Math.max(75, width / aspect); // get a height either proportional to that or 75 px
        const height = this.props.rowHeight() ? this.props.rowHeight() - (this.props.padding || 6) * 2 : undefined;
        const width = height ? height * aspect : undefined; // increase the width of the image if necessary to maintain proportionality

        return <img src={this.url} width={width} height={height} style={{}} draggable="false" onPointerEnter={this.showHoverPreview} onPointerMove={this.moveHoverPreview} onPointerLeave={this.removeHoverPreview} />;
    }
}

@observer
export class SchemaDateCell extends React.Component<SchemaTableCellProps> {
    @observable _pickingDate: boolean = false;

    @computed get date(): DateField {
        // if the cell is a date field, cast then contents to a date. Otherrwwise, make the contents undefined.
        return DateCast(this.props.Document[this.props.fieldKey]);
    }

    @action
    handleChange = (date: any) => {
        // const script = CompileScript(date.toString(), { requiredType: "Date", addReturn: true, params: { this: Doc.name } });
        // if (script.compiled) {
        //     this.applyToDoc(this._document, this.props.row, this.props.col, script.run);
        // } else {
        // ^ DateCast is always undefined for some reason, but that is what the field should be set to
        this.props.Document[this.props.fieldKey] = new DateField(date as Date);
        //}
    };

    render() {
        return <DatePicker dateFormat={'Pp'} selected={this.date.date} onChange={(date: any) => this.handleChange(date)} />;
    }
}
@observer
export class SchemaRTFCell extends React.Component<SchemaTableCellProps> {
    @computed get selected() {
        const selected: [Doc, number] | undefined = this.props.selectedCell();
        return this.props.isRowActive() && selected?.[0] === this.props.Document && selected[1] === this.props.col;
    }
    selectedFunc = () => this.selected;
    render() {
        const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this.props);
        fieldProps.isContentActive = this.selectedFunc;
        return (
            <div className="schemaRTFCell" style={{ display: 'flex', fontStyle: this.selected ? undefined : 'italic', width: '100%', height: '100%', position: 'relative', color, textDecoration, cursor, pointerEvents }}>
                {this.selected ? <FormattedTextBox {...fieldProps} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))}
            </div>
        );
    }
}
@observer
export class SchemaBoolCell extends React.Component<SchemaTableCellProps> {
    @computed get selected() {
        const selected: [Doc, number] | undefined = this.props.selectedCell();
        return this.props.isRowActive() && selected?.[0] === this.props.Document && selected[1] === this.props.col;
    }
    render() {
        const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this.props);
        return (
            <div className="schemaBoolCell" style={{ display: 'flex', color, textDecoration, cursor, pointerEvents }}>
                <input
                    style={{ marginRight: 4 }}
                    type="checkbox"
                    checked={BoolCast(this.props.Document[this.props.fieldKey])}
                    onChange={undoBatch((value: React.ChangeEvent<HTMLInputElement> | undefined) => {
                        if ((value?.nativeEvent as any).shiftKey) {
                            this.props.setColumnValues(this.props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString());
                        }
                        KeyValueBox.SetField(this.props.Document, this.props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString());
                    })}
                />
                <EditableView
                    contents={<FieldView {...fieldProps} />}
                    editing={this.selected ? undefined : false}
                    GetValue={() => Field.toKeyValueString(this.props.Document, this.props.fieldKey)}
                    SetValue={undoBatch((value: string, shiftDown?: boolean, enterKey?: boolean) => {
                        if (shiftDown && enterKey) {
                            this.props.setColumnValues(this.props.fieldKey.replace(/^_/, ''), value);
                        }
                        const set = KeyValueBox.SetField(this.props.Document, this.props.fieldKey.replace(/^_/, ''), value);
                        this.props.finishEdit?.();
                        return set;
                    })}
                />
            </div>
        );
    }
}
