import { css, DJElement, h, html, qs } from "./util";

//@ts-check
const MPG_T3_SYN = 25;
const MPG_T4_SYN = 100;

class GlobalEventBus {
    /**
     * @private
     * @type {{ onChange(): void }[]}
     */
    changeSubscribers = [];

    /**
     * @param {{ onChange(): void }} listener
     */
    addListener(listener) {
        this.changeSubscribers.push(listener);
    }

    /**
     * @param {any} sender
     */
    sendChange(sender) {
        for (const sub of this.changeSubscribers) {
            if (sub === sender) continue;
            sub.onChange();
        }
    }
}

const eventBus = new GlobalEventBus();

class GrainInput {
    /** @type GrainInput[] */
    static inputs = [];
    /** @type string */
    name;
    /** @type string */
    unit;
    /** @type number */
    mpg;
    /** @type HTMLInputElement */
    inputEl;
    /** @type GrainInput */
    reference;

    /**
     * @param {{
     *     name: string,
     *     mpg: number,
     *     reference: GrainInput | 'self',
     *     unit: string,
     *     step?: number,
     * }} opts
     */
    constructor(opts) {
        this.name = opts.name;
        this.mpg = opts.mpg;
        this.unit = opts.unit;
        this.reference = opts.reference === "self" ? this : opts.reference;
        this.inputEl = h("input", { type: "number", min: 0, step: opts.step ?? 1 });
        eventBus.addListener(this);
    }

    /**
     * @param {HTMLElement} insertionPoint
     */
    attachInput(insertionPoint) {
        insertionPoint.appendChild(this.inputEl);
        this.inputEl.valueAsNumber = this.mpg;
        this.inputEl.addEventListener("input", (e) => {
            const newVal = /** @type HTMLInputElement | null */ (e.currentTarget)?.valueAsNumber ?? 0;
            if (this !== this.reference) {
                this.reference.inputEl.valueAsNumber = newVal / this.mpg;
            }
            eventBus.sendChange(this);
        });
    }

    onChange() {
        if (!this.reference || this === this.reference) return;
        this.inputEl.valueAsNumber = this.mpg * this.reference.inputEl.valueAsNumber;
    }

    getCurrentValue() {
        return this.inputEl.valueAsNumber;
    }
}

class RatiosController {
    /** @type HTMLInputElement */
    t3Ratio = h("input", { type: "number", min: 0, step: 1 });
    /** @type HTMLInputElement */
    t4Ratio = h("input", { type: "number", min: 0, step: 1 });
    /** @type HTMLInputElement */
    t3Syn = h("input", { type: "number", min: 0 });
    /** @type HTMLInputElement */
    t4Syn = h("input", { type: "number", min: 0 });
    /** @type number */
    t3 = 1;
    /** @type number */
    t4 = 4;
    /** @type GrainInput */
    reference;

    /**
     * @param {GrainInput} referenceInput
     */
    constructor(referenceInput) {
        this.reference = referenceInput;
    }

    /**
     * @param {Record<string, HTMLElement>} inputs
     */
    attachInputs(inputs) {
        inputs.t3Ratio.replaceWith(this.t3Ratio);
        inputs.t4Ratio.replaceWith(this.t4Ratio);
        inputs.t3Syn.replaceWith(this.t3Syn);
        inputs.t4Syn.replaceWith(this.t4Syn);

        this.t3Ratio.addEventListener("input", (e) => {
            const newVal = /** @type HTMLInputElement | null */ (e.currentTarget)?.valueAsNumber ?? 0;
            this.t3 = newVal;
            this.onChange();
        });

        this.t4Ratio.addEventListener("input", (e) => {
            const newVal = /** @type HTMLInputElement | null */ (e.currentTarget)?.valueAsNumber ?? 0;
            this.t4 = newVal;
            this.onChange();
        });

        this.t3Syn.addEventListener("input", (e) => {
            const newVal = /** @type HTMLInputElement | null */ (e.currentTarget)?.valueAsNumber ?? 0;
            this.reference.inputEl.valueAsNumber = newVal / MPG_T3_SYN + this.t4Syn.valueAsNumber / MPG_T4_SYN;
            this.t3 = newVal;
            this.t4 = this.t4Syn.valueAsNumber;
            this.updateRatio();
            this.updateSyn();
            eventBus.sendChange(this);
        });

        this.t4Syn.addEventListener("input", (e) => {
            const newVal = /** @type HTMLInputElement | null */ (e.currentTarget)?.valueAsNumber ?? 0;
            this.reference.inputEl.valueAsNumber = newVal / MPG_T4_SYN + this.t3Syn.valueAsNumber / MPG_T3_SYN;
            this.t3 = this.t3Syn.valueAsNumber;
            this.t4 = newVal;
            this.updateRatio();
            this.updateSyn();
            eventBus.sendChange(this);
        });

        eventBus.addListener(this);
        this.onChange();
    }

    onChange() {
        this.updateRatio();
        this.updateSyn();
    }

    updateRatio() {
        this.t3Ratio.valueAsNumber = this.t3;
        this.t4Ratio.valueAsNumber = this.t4;
    }

    updateSyn() {
        const total = this.t3 + this.t4;
        const t3Proportion = this.t3 / total;
        const t4Proportion = this.t4 / total;
        const grainsSyn = t3Proportion / MPG_T3_SYN + t4Proportion / MPG_T4_SYN;
        const multiplierSyn = this.reference.getCurrentValue() / grainsSyn;
        this.t3Syn.valueAsNumber = t3Proportion * multiplierSyn;
        this.t4Syn.valueAsNumber = t4Proportion * multiplierSyn;
    }
}

class ThyroidConverter extends DJElement {
    static styles = css`
        table {
            border-collapse: separate;
            margin: auto;
            margin-top: 20px;
            margin-bottom: 20px;
            border-radius: 5px;
            border-spacing: 0;
            border: 1px solid var(--text-color);
        }

        input {
            width: 70px;
        }
        tr:last-of-type td {
            border-bottom: none;
        }
        td {
            border-bottom: 1px solid var(--text-color);
            border-right: 1px solid var(--text-color);
            padding: 10px;
        }
        td:last-of-type {
            border-right: none;
        }
        .breathe {
            padding-left: 4px;
            padding-right: 4px;
        }
        @media (min-width: 600px) {
            td.right div {
                display: inline-block;
            }
        }
    `;

    static template = html`
<table>
    <tbody id="table-body">
        <tr id="compounded-start" class="ratios">
            <td>
                Desired Ratio (T3:T4)
            </td>
            <td class="right">
                <div><slot class="t3"></slot></div> :
                <div><slot class="t4"></slot></div>
            </td>
        </tr>
        <tr class="synthetic">
            <td>
                Synthetic T3/T4 Combo
            </td>
            <td class="right">
                <div><slot class="t3"></slot>mcg</div> :
                <div><slot class="t4"></slot>mcg</div>
            </td>
        </tr>
    </tbody>
</table>`;

    constructor() {
        super();
        const mainGrainInput = new GrainInput({
            name: "Grains",
            mpg: 1,
            reference: "self",
            unit: "",
            step: 0.1,
        });

        const ratiosController = new RatiosController(mainGrainInput);

        const inputs = [
            mainGrainInput,
            new GrainInput({
                name: "Armour, Natural Dessicated Thyroid",
                mpg: 60,
                unit: "mg",
                reference: mainGrainInput,
            }),
            new GrainInput({
                name: 'Liothyronine (Triiodothyronine, "Cytomel", T3)',
                mpg: MPG_T3_SYN,
                unit: "mcg",
                reference: mainGrainInput,
            }),
            new GrainInput({
                name: 'Levothyroxine (Thyroxine, "Cynoplus", T4)',
                mpg: MPG_T4_SYN,
                unit: "mcg",
                reference: mainGrainInput,
            }),
        ];

        const tableBody = qs("#table-body", this.root);
        const compoundedStart = qs("#compounded-start", this.root);

        for (const field of inputs) {
            const newRow = h("tr", {}, [
                h("td", { textContent: field.name }),
                h("td", {}, [
                    h("div", { className: "input", style: "display: inline-block;" }),
                    h("span", { className: "breathe", textContent: field.unit }),
                ]),
            ]);
            field.attachInput(qs("div.input", newRow));
            tableBody.insertBefore(newRow, compoundedStart);
        }

        ratiosController.attachInputs({
            t3Ratio: qs(".ratios .t3", tableBody),
            t4Ratio: qs(".ratios .t4", tableBody),
            t3Syn: qs(".synthetic .t3", tableBody),
            t4Syn: qs(".synthetic .t4", tableBody),
        });
    }
}
customElements.define("thyroid-converter", ThyroidConverter);
