import * as React from 'react';
import { Issue } from './reportManagerSchema';
import { darkColors, dashBlue, getLabelColors, isDarkMode, lightColors } from './reportManagerUtils';
import ReactMarkdown from 'react-markdown';
import rehypeRaw from 'rehype-raw';
import remarkGfm from 'remark-gfm';
import { StrCast } from '../../../fields/Types';
import { Doc } from '../../../fields/Doc';

/**
 * Mini helper components for the report component.
 */

interface FilterProps<T> {
    items: T[];
    activeValue: T | null;
    setActiveValue: (val: T | null) => void;
}

// filter ui for issues (horizontal list of tags)
export const Filter = <T extends string>({ items, activeValue, setActiveValue }: FilterProps<T>) => {
    // establishing theme
    const darkMode = isDarkMode(StrCast(Doc.UserDoc().userBackgroundColor));
    const colors = darkMode ? darkColors : lightColors;
    const isTagDarkMode = isDarkMode(StrCast(Doc.UserDoc().userColor));
    const activeTagTextColor = isTagDarkMode ? darkColors.text : lightColors.text;

    return (
        <div className="issues-filter">
            <Tag
                text={'All'}
                onClick={() => {
                    setActiveValue(null);
                }}
                fontSize="12px"
                backgroundColor={activeValue === null ? StrCast(Doc.UserDoc().userColor) : 'transparent'}
                color={activeValue === null ? activeTagTextColor : colors.textGrey}
                borderColor={activeValue === null ? StrCast(Doc.UserDoc().userColor) : colors.border}
                border
            />
            {items.map(item => {
                return (
                    <Tag
                        key={item}
                        text={item}
                        onClick={() => {
                            setActiveValue(item);
                        }}
                        fontSize="12px"
                        backgroundColor={activeValue === item ? StrCast(Doc.UserDoc().userColor) : 'transparent'}
                        color={activeValue === item ? activeTagTextColor : colors.textGrey}
                        border
                        borderColor={activeValue === item ? StrCast(Doc.UserDoc().userColor) : colors.border}
                    />
                );
            })}
        </div>
    );
};

interface IssueCardProps {
    issue: Issue;
    onSelect: () => void;
}

// Component for the issue cards list on the left
export const IssueCard = ({ issue, onSelect }: IssueCardProps) => {
    const [textColor, setTextColor] = React.useState('');
    const [bgColor, setBgColor] = React.useState('transparent');
    const [borderColor, setBorderColor] = React.useState('transparent');

    const resetColors = () => {
        const darkMode = isDarkMode(StrCast(Doc.UserDoc().userBackgroundColor));
        const colors = darkMode ? darkColors : lightColors;
        setTextColor(colors.text);
        setBorderColor(colors.border);
        setBgColor('transparent');
    };

    const handlePointerOver = () => {
        const darkMode = isDarkMode(StrCast(Doc.UserDoc().userColor));
        setTextColor(darkMode ? darkColors.text : lightColors.text);
        setBorderColor(StrCast(Doc.UserDoc().userColor));
        setBgColor(StrCast(Doc.UserDoc().userColor));
    };

    React.useEffect(() => {
        resetColors();
    }, []);

    return (
        <div className="issue-card" onClick={onSelect} style={{ color: textColor, backgroundColor: bgColor, borderColor: borderColor }} onPointerOver={handlePointerOver} onPointerOut={resetColors}>
            <div className="issue-top">
                <label className="issue-label">#{issue.number}</label>
                <div className="issue-tags">
                    {issue.labels.map(label => {
                        const labelString = typeof label === 'string' ? label : label.name ?? '';
                        const colors = getLabelColors(labelString);
                        return <Tag key={labelString} text={labelString} backgroundColor={colors[0]} color={colors[1]} />;
                    })}
                </div>
            </div>
            <h3 className="issue-title">{issue.title}</h3>
        </div>
    );
};

interface IssueViewProps {
    issue: Issue;
}

// Detailed issue view that displays on the right
export const IssueView = ({ issue }: IssueViewProps) => {
    const [issueBody, setIssueBody] = React.useState('');

    // Parses the issue body into a formatted markdown (main functionality is replacing urls with tags)
    const parseBody = async (body: string) => {
        const imgTagRegex = /<img\b[^>]*\/?>/;
        const videoTagRegex = /<video\b[^>]*\/?>/;
        const audioTagRegex = /<audio\b[^>]*\/?>/;

        const fileRegex = /https:\/\/browndash\.com\/files/;
        const parts = body.split('\n');

        const modifiedParts = await Promise.all(
            parts.map(async part => {
                if (imgTagRegex.test(part) || videoTagRegex.test(part) || audioTagRegex.test(part)) {
                    return `\n${await parseFileTag(part)}\n`;
                } else if (fileRegex.test(part)) {
                    const tag = await parseDashFiles(part);
                    return tag;
                } else {
                    return part;
                }
            })
        );

        setIssueBody(modifiedParts.join('\n'));
    };

    // Extracts the src from an image tag and either returns the raw url if not accessible or a new image tag
    const parseFileTag = async (tag: string): Promise<string> => {
        const regex = /src="([^"]+)"/;
        let url = '';
        const match = tag.match(regex);
        if (!match) return tag;
        url = match[1];
        if (!url) return tag;

        const mimeType = url.split('.').pop();
        if (!mimeType) return tag;

        switch (mimeType) {
            // image
            case '.jpg':
            case '.png':
            case '.jpeg':
            case '.gif':
                return await getDisplayedFile(url, 'image');
            // video
            case '.mp4':
            case '.mpeg':
            case '.webm':
            case '.mov':
                return await getDisplayedFile(url, 'video');
            //audio
            case '.mp3':
            case '.wav':
            case '.ogg':
                return await getDisplayedFile(url, 'audio');
        }
        return tag;
    };

    // Returns the corresponding HTML tag for a src url
    const parseDashFiles = async (url: string) => {
        const dashImgRegex = /https:\/\/browndash\.com\/files[/\\]images/;
        const dashVideoRegex = /https:\/\/browndash\.com\/files[/\\]videos/;
        const dashAudioRegex = /https:\/\/browndash\.com\/files[/\\]audio/;

        if (dashImgRegex.test(url)) {
            return await getDisplayedFile(url, 'image');
        } else if (dashVideoRegex.test(url)) {
            return await getDisplayedFile(url, 'video');
        } else if (dashAudioRegex.test(url)) {
            return await getDisplayedFile(url, 'audio');
        } else {
            return url;
        }
    };

    const getDisplayedFile = async (url: string, fileType: 'image' | 'video' | 'audio'): Promise<string> => {
        switch (fileType) {
            case 'image':
                const imgValid = await isImgValid(url);
                if (!imgValid) return `\n${url} (This image could not be loaded)\n`;
                return `\n${url}\n<img width="100%" alt="Issue asset" src=${url} />\n`;
            case 'video':
                const videoValid = await isVideoValid(url);
                if (!videoValid) return `\n${url} (This video could not be loaded)\n`;
                return `\n${url}\n<video class="report-default-video" width="100%" controls alt="Issue asset" src=${url} />\n`;
            case 'audio':
                const audioValid = await isAudioValid(url);
                if (!audioValid) return `\n${url} (This audio could not be loaded)\n`;
                return `\n${url}\n<audio src=${url} controls />\n`;
        }
    };

    // Loads an image and returns a promise that resolves as whether the image is valid or not
    const isImgValid = (src: string): Promise<boolean> => {
        const imgElement = document.createElement('img');
        const validPromise: Promise<boolean> = new Promise(resolve => {
            imgElement.addEventListener('load', () => resolve(true));
            imgElement.addEventListener('error', () => resolve(false));
        });
        imgElement.src = src;
        return validPromise;
    };

    // Loads a video and returns a promise that resolves as whether the video is valid or not
    const isVideoValid = (src: string): Promise<boolean> => {
        const videoElement = document.createElement('video');
        const validPromise: Promise<boolean> = new Promise(resolve => {
            videoElement.addEventListener('loadeddata', () => resolve(true));
            videoElement.addEventListener('error', () => resolve(false));
        });
        videoElement.src = src;
        return validPromise;
    };

    // Loads audio and returns a promise that resolves as whether the audio is valid or not
    const isAudioValid = (src: string): Promise<boolean> => {
        const audioElement = document.createElement('audio');
        const validPromise: Promise<boolean> = new Promise(resolve => {
            audioElement.addEventListener('loadeddata', () => resolve(true));
            audioElement.addEventListener('error', () => resolve(false));
        });
        audioElement.src = src;
        return validPromise;
    };

    // Called on mount to parse the body
    React.useEffect(() => {
        setIssueBody('Loading...');
        parseBody((issue.body as string) ?? '');
    }, [issue]);

    return (
        <div className="issue-view">
            <span className="issue-label">
                Issue{' '}
                <a className="issue-link" href={issue.html_url} target="_blank">
                    #{issue.number}
                </a>
            </span>
            <h2 className="issue-title">{issue.title}</h2>
            <div className="issue-date">
                Opened on {new Date(issue.created_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })} {issue.user?.login && `by ${issue.user?.login}`}
            </div>
            {issue.labels.length > 0 && (
                <div>
                    <div className="issue-tags">
                        {issue.labels.map(label => {
                            const labelString = typeof label === 'string' ? label : label.name ?? '';
                            const colors = getLabelColors(labelString);
                            return <Tag key={labelString} text={labelString} backgroundColor={colors[0]} color={colors[1]} fontSize="12px" />;
                        })}
                    </div>
                </div>
            )}
            <ReactMarkdown children={issueBody} className="issue-content" linkTarget={'_blank'} remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]} />
        </div>
    );
};

interface TagProps {
    text: string;
    fontSize?: string;
    color?: string;
    backgroundColor?: string;
    borderColor?: string;
    border?: boolean;
    onClick?: () => void;
}

// Small tag for labels of the issue
export const Tag = ({ text, color, backgroundColor, fontSize, border, borderColor, onClick }: TagProps) => {
    return (
        <div
            onClick={onClick ?? (() => {})}
            className="report-tag"
            style={{ color: color ?? '#ffffff', backgroundColor: backgroundColor ?? '#347bff', cursor: onClick ? 'pointer' : 'auto', fontSize: fontSize ?? '10px', border: border ? '1px solid' : 'none', borderColor: borderColor ?? '#94a3b8' }}>
            {text}
        </div>
    );
};

interface FormInputProps {
    value: string;
    placeholder: string;
    onChange: (val: string) => void;
}
export const FormInput = ({ value, placeholder, onChange }: FormInputProps) => {
    const [inputBorderColor, setInputBorderColor] = React.useState('');

    return (
        <input
            className="report-input"
            style={{ borderBottom: `1px solid ${inputBorderColor}` }}
            value={value}
            type="text"
            placeholder={placeholder}
            onChange={e => onChange(e.target.value)}
            required
            onPointerOver={() => {
                if (inputBorderColor === dashBlue) return;
                setInputBorderColor(isDarkMode(StrCast(Doc.UserDoc().userBackgroundColor)) ? darkColors.textGrey : lightColors.textGrey);
            }}
            onPointerOut={() => {
                if (inputBorderColor === dashBlue) return;
                setInputBorderColor(isDarkMode(StrCast(Doc.UserDoc().userBackgroundColor)) ? darkColors.border : lightColors.border);
            }}
            onFocus={() => {
                setInputBorderColor(dashBlue);
            }}
            onBlur={() => {
                setInputBorderColor(isDarkMode(StrCast(Doc.UserDoc().userBackgroundColor)) ? darkColors.border : lightColors.border);
            }}
        />
    );
};

export const FormTextArea = ({ value, placeholder, onChange }: FormInputProps) => {
    const [textAreaBorderColor, setTextAreaBorderColor] = React.useState('');

    return (
        <textarea
            className="report-textarea"
            value={value}
            placeholder={placeholder}
            onChange={e => onChange(e.target.value)}
            required
            style={{ border: `1px solid ${textAreaBorderColor}` }}
            onPointerOver={() => {
                if (textAreaBorderColor === dashBlue) return;
                setTextAreaBorderColor(isDarkMode(StrCast(Doc.UserDoc().userBackgroundColor)) ? darkColors.textGrey : lightColors.textGrey);
            }}
            onPointerOut={() => {
                if (textAreaBorderColor === dashBlue) return;
                setTextAreaBorderColor(isDarkMode(StrCast(Doc.UserDoc().userBackgroundColor)) ? darkColors.border : lightColors.border);
            }}
            onFocus={() => {
                setTextAreaBorderColor(dashBlue);
            }}
            onBlur={() => {
                setTextAreaBorderColor(isDarkMode(StrCast(Doc.UserDoc().userBackgroundColor)) ? darkColors.border : lightColors.border);
            }}
        />
    );
};
