import { nextFrame } from "@common/utils";
import { Color, GAME_HEIGHT, GAME_WIDTH } from "./const";
import type { IChar, IColorLike, IRegion } from "./types";
import { generateColors, randChar } from "./utils";

const ROOT_NODE = document.getElementById('root');
const FPS_NODE = document.getElementById('fps');

const COLORS = generateColors();
const GAME_FIELD = generateField();

function handleResize() {
    const windowWidth = window.innerWidth;
    const windowHeight = window.innerHeight;

    const charWidth = windowWidth / GAME_WIDTH;
    const charHeight = windowHeight / GAME_HEIGHT;

    // TODO
}

function generateField(width = GAME_WIDTH, height = GAME_HEIGHT, parent = ROOT_NODE): HTMLSpanElement[][] {
    if (!parent) return [];

    let field: HTMLSpanElement[][] = [];
    parent.textContent = '';

    for (let row = 0; row < height; row++) {
        const line = document.createElement('div');
        field[row] = [];

        for (let column = 0; column < width; column++) {
            const span = document.createElement('span');
            span.innerHTML = randChar('!');
            span.style.color = COLORS[7];
            line.append(span);

            field[row][column] = span;
        }

        parent.append(line);
    }

    return field;
}

export function setChar(x: number, y: number, [char, fg = 'white', bg = 'black']: IChar = ['█']) {
    if (x < 0 || y < 0 || y >= GAME_HEIGHT || x >= GAME_WIDTH || !char) {
        return;
    }
    const span = GAME_FIELD[y | 0][x | 0];

    if (typeof fg === 'number') fg = COLORS[fg];
    if (typeof bg === 'number') bg = COLORS[bg];

    if (char === ' ') {
        span.innerHTML = '&nbsp;';
    } else {
        span.textContent = char.toString();
        span.style.color = fg;
        span.style.backgroundColor = bg;
    }
}

export function getChar(x: number, y: number): IChar {
    if (x < 0 || y < 0 || y >= GAME_HEIGHT || x >= GAME_WIDTH) {
        return [' ', COLORS[0], COLORS[1]];
    }

    const span = GAME_FIELD[y | 0][x | 0];

    return [
        span.textContent?.[0] ?? ' ',
        span.style.color,
        span.style.backgroundColor,
    ];
}

export function setRegion(x: number, y: number, w: number, h: number, region: IRegion) {
    for (let screenY = y; screenY < y + h; screenY++) {
        for (let screenX = x; screenX < x + w; screenX++) {
            const char = region[screenY - y][screenX - x]
            setChar(screenX, screenY, char);
        }
    }
}

export function getRegion(x: number, y: number, w: number, h: number) {
    const region: IRegion = [];
    for (let screenY = y; screenY < y + h; screenY++) {
        const line: IChar[] = []
        for (let screenX = x; screenX < x + w; screenX++) {
            line.push(getChar(screenX, screenY));
        }
        region.push(line);
    }

    return region;
}

export function drawText(x: number, y: number, text: string, fg: IColorLike = Color.WHITE, bg: IColorLike = Color.BLACK) {
    if (text.includes('\n')) {
        const lines = text.split('\n');
        for (let line = 0; line < lines.length; line++) {
            drawText(x, y + line, lines[line], fg, bg);
        }
    } else {
        for (let i = 0; i < text.length; i++) {
            setChar(x + i, y, [text[i], fg, bg]);
        }
    }
}

export function drawVLine(x: number, y1: number, y2: number, char: IChar = ['│']) {
    if (y2 < y1) {
        const t = y2;
        y2 = y1;
        y1 = t;
    }
    for (let y = y1; y <= y2; y++) {
        setChar(x, y, char);
    }
}

export function drawHLine(x1: number, x2: number, y: number, char: IChar = ['─']) {
    if (x2 < x1) {
        const t = x2;
        x2 = x1;
        x1 = t;
    }
    for (let x = x1; x <= x2; x++) {
        setChar(x, y, char);
    }
}

export const isVertical = (char: string) => char === '│';
export const isHorizontal = (char: string) => char === '─';
export const isCorner = (char: string) => '┌┐└┘'.includes(char);

interface IBoxOptions {
    vertical?: string;
    horizontal?: string;
    topLeft?: string;
    topRight?: string;
    bottomLeft?: string;
    bottomRight?: string;
    fill?: IChar;
    fg?: IColorLike;
    bg?: IColorLike;
    title?: string;
}
export function drawBox(x: number, y: number, width: number, height: number, options: IBoxOptions = {}) {
    const {
        vertical = '│',
        horizontal = '─',
        topLeft = '┌',
        topRight = '┐',
        bottomLeft = '└',
        bottomRight = '┘',
        fg = Color.WHITE,
        bg = Color.BLACK,
        fill,
        title,
    } = options;

    setChar(x, y, [topLeft, fg, bg]);
    setChar(x + width + 1, y, [topRight, fg, bg]);
    setChar(x, y + height + 1, [bottomLeft, fg, bg]);
    setChar(x + width + 1, y + height + 1, [bottomRight, fg, bg]);

    if (fill) {
        fillBox(x + 1, y + 1, width, height, fill);
    }

    drawHLine(x + 1, x + width, y, [horizontal, fg, bg]);
    drawHLine(x + 1, x + width, y + height + 1, [horizontal, fg, bg]);

    drawVLine(x, y + 1, y + height, [vertical, fg, bg]);
    drawVLine(x + width + 1, y + 1, y + height, [vertical, fg, bg]);

    if (title) {
        drawText(x + 1, y, title, fg, bg);
    }
}

export function drawTextInBox(x: number, y: number, text: string, options: IBoxOptions = {}) {
    const {
        fg = Color.WHITE,
        bg = Color.BLACK,
    } = options;
    let width = 0;
    const lines = text.split('\n');
    const height = lines.length;

    for (const line of lines) {
        if (line.length > width) {
            width = line.length;
        }
    }

    drawBox(x, y, width, height, { ...options, fill: [' '] });
    drawText(x + 1, y + 1, text, fg, bg);
}

export function fillBox(x: number, y: number, width: number, height: number, char: IChar = ['█']) {
    for (let i = y; i < y + height; i++) {
        drawHLine(x, x + width - 1, i, char);
    }
}

let prevTime = 0;
let dt = 0;

export async function tick(desiredFPS = 60) {
    const time = await nextFrame();
    dt = time - prevTime;
    prevTime = time;
    if (FPS_NODE) {
        FPS_NODE.textContent = `${Math.round(1000 / dt)}`;
    }

    const totalDelay = 1000 / desiredFPS;
    const remainingDelay = totalDelay - dt;
    if (remainingDelay > 4) {
        await Bun.sleep(remainingDelay);
    }
}

window.addEventListener('resize', handleResize);
handleResize();
