import type * as csstype from "csstype";
import * as tester from "./tester";

// based on cxs

interface ISheet {
    insertRule(rule: string): void;
}

class Sheet {
    domSheet: CSSStyleSheet | null;
    rulesCount = 0;
    constructor() {
        let el = document.createElement("style");
        el = document.head.appendChild(el);
        if (!el.sheet) {
            console.error("failed to created style element");
        }
        this.domSheet = el.sheet;
    }

    insertRule(rule: string) {
        this.domSheet!.insertRule(rule, this.rulesCount);
        this.rulesCount++;
    }
}

class FakeSheet {
    insertRule(rule: string) {

    }
}

let sheet: ISheet = BROWSER ? new Sheet() : new FakeSheet();

export type RuleDefinition = csstype.StandardProperties<string, string> & {
    [key: string]: string | number | RuleDefinition;
}

function combineSelectorForNestedRule(selector: string, child: string): string {
    let result: string[] = [];
    for (let p of selector.split(',')) {
        p = p.trim();
        for (let c of child.split(',')) {
            c = c.trim();
            if (c.startsWith(':')) {
                result.push(p + c);
            } else if (c.startsWith("&")) {
                result.push(selector + c.substring(1))
            } else if (c.startsWith('@')) {
                result.push(c + ' ' + p)
            } else {
                result.push(p + ' ' + c)
            }
        }
    }
    return result.join(', ');
}

export function block(body: string) {
    sheet.insertRule(body);
}

export function rule(sel: string, body: RuleDefinition) {
    let subrules: [string, RuleDefinition][] = [];
    let bodyLines: string[] = [];
    for (const [key, value] of Object.entries(body)) {
        if (typeof value === 'object') {
            subrules.push([combineSelectorForNestedRule(sel, key), value]);
            continue;
        } else {
            let property = key.replace(/[A-Z]/g, c => '-' + c.toLowerCase());
            bodyLines.push(`${property}: ${value}`);
        }
    }
    sheet.insertRule(sel + "{" + bodyLines.join(";") + "}\n");
    for (let [sel1, body1] of subrules) {
        rule(sel1, body1);
    }
}

const usedNames = new Set<string>();
export function cls(name: string, body: RuleDefinition) {
    if (usedNames.has(name)) {
        // find a new name
        for (let i = 0; i < 100; i++) {
            let newName = name + '-' + i;
            if (!usedNames.has(newName)) {
                name = newName;
                break;
            }
        }
    }
    usedNames.add(name);
    rule("." + name, body);
    return name;
}

export function tests() {
    tester.test('css rule combination', (t: tester.Test) => {
        {
            let sel = ".abc";
            let child = "p";
            let expected = ".abc p";
            t.equal(expected, combineSelectorForNestedRule(sel, child), "expected", "actual");
        }
        {
            let sel = ".abc";
            let child = "p, b";
            let expected = ".abc p, .abc b";
            t.equal(expected, combineSelectorForNestedRule(sel, child), "expected", "actual");
        }
        {
            // pseudo selectors
            let sel = ".abc";
            let child = ":hover";
            let expected = ".abc:hover";
            t.equal(expected, combineSelectorForNestedRule(sel, child), "expected", "actual");
        }
    })
}
