/*
    Hurry Curry! - a game about cooking
    Copyright (C) 2026 Hurry Curry! Contributors

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, version 3 of the License only.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.

*/

//? Is this a good idea? Probably not.

// interface Recipe {
//     tile?: string,
//     inputs: (Item | null | undefined)[],
//     outputs: (Item | null | undefined)[],
//     action: "instant" | "passive" | "active" | "demand" | "demand"
//     duration?: number
//     revert_duration?: number,
//     warn?: boolean,
//     points?: number,
// }

const all_items = new Set()
const all_recipes = new Set()
function out(r) {
    r.inputs.forEach(i => i ? all_items.add(i) : void 0)
    r.outputs.forEach(i => i ? all_items.add(i) : void 0)
    r.inputs = r.inputs.filter(e => e)
    r.outputs = r.outputs.filter(e => e)
    all_recipes.add(r);
}
function finish() {
    const k = new Set()
    for (const r of all_recipes) {
        let s = `- action: !${r.action}\n`
        s += `  inputs: [${r.inputs.map(e => JSON.stringify(e.toString())).join(",")}]\n`
        s += `  outputs: [${r.outputs.map(e => JSON.stringify(e.toString())).join(",")}]\n`
        if (r.warn) s += `  warn: true\n`
        if (r.group) s += `  group: ${r.group}\n`
        if (r.inputs[0]?.group_hidden) s += `  group_hidden: true\n`
        if (r.duration) s += `  duration: ${r.duration}\n`
        if (r.revert_duration) s += `  revert_duration: ${r.revert_duration}\n`
        if (r.points) s += `  points: ${r.points}\n`
        if (r.tile) s += `  tile: ${r.tile}\n`
        k.add(s)
    }
    for (const r of k)
        console.log(r);
}
function auto_info(name) {
    if (name == "plate") return { dispose_out: new Item("dirty-plate") }
    if (name == "foodprocessor") return {}
    if (name == "pan") return {}
    if (name == "pot") return {}
    if (name == "basket") return {}
    if (name == "dirty-plate") return {}
    if (name == "glass") return { dispose_tile: "sink" }
    if (name == "burned") return { dispose_self: true }
    return { dispose_self: true, burnable: true }
}
function auto_trash() {
    for (const i of [...all_items]) {
        const info = auto_info(i.container?.name ?? i.name)
        if (!info.dispose_self && !i.container) continue
        out({
            action: "instant",
            inputs: [new Item(i.name, i.container)],
            outputs: info.dispose_self ? [] : [info.dispose_out ?? i.container],
            tile: info.dispose_tile ?? "trash"
        })
    }
}
function auto_burn() {
    for (const i of [...all_items]) {
        const info = auto_info(i.container?.name ?? i.name)
        if (!info.burnable) continue
        out({
            action: "passive",
            inputs: [new Item(i.name, i.container)],
            outputs: [new Item("burned")],
            duration: 1.5,
            revert_duration: 1.5,
            warn: true,
            tile: "stove"
        })
    }
}
class Item {
    constructor(name, container) {
        if (typeof name != "string") throw new Error("" + name);
        if (container && !(container instanceof Item)) throw new Error("" + container);
        this.name = name;
        this.container = container
    }
    as(s) { this.name = s; return this }
    tr_nested(container) {
        const o = new Item(this.toString(), container)
        out({ action: "instant", inputs: [container, this], outputs: [o] })
        return o
    }
    tr(container) {
        const o = new Item(this.name, container)
        if (this.container?.name == container?.name) return o
        if (this.container) out({ action: "instant", inputs: [this, container], outputs: [this.container, o] })
        else out({ action: "instant", inputs: [container, this], outputs: [o] })
        return o
    }
    toString() {
        return (this.container ? this.container + ":" : "") + this.name
    }
}

function crate(s) {
    const item = new Item(s);
    out({ action: "instant", inputs: [], outputs: [item], tile: `crate\:${s}`, points: -1 })
    out({ action: "instant", inputs: [item], outputs: [], tile: `crate\:${s}`, points: 1 })
    return item
}

const FP = new Item("foodprocessor")
const POT = new Item("pot")
const PAN = new Item("pan")
const PL = crate("plate")
const GL = crate("glass")
const BA = new Item("basket")

function cut(s, two, duration = 2) {
    const o = new Item(`sliced-${s.name}`, s.container)
    out({ action: "active", inputs: [s], outputs: [o, two ? o : null], tile: "cutting-board", duration })
    return o
}
function roll(s, two) {
    const o = new Item(`rolled-${s.name}`, s.container)
    out({ action: "active", inputs: [s], outputs: [o, two ? o : null], tile: "rolling-board", duration: 2 })
    return o
}
function deep_fry(s, duration = 15) {
    const o = new Item(`deep-fried-${s.name}`, s.container)
    out({ action: "passive", duration, revert_duration: duration * 2, tile: "deep-fryer", inputs: [s], outputs: [o] })
    out({ action: "passive", duration: duration / 2, revert_duration: duration / 3, tile: "deep-fryer", inputs: [o], outputs: [new Item("burned", BA)], warn: true })
    return o
}
function cook(s, duration = 20) {
    const o = new Item(`cooked-${s.name}`, s.container)
    out({ action: "passive", duration, revert_duration: duration * 2, tile: "stove", inputs: [s], outputs: [o] })
    out({ action: "passive", duration: duration / 3, revert_duration: duration / 2, tile: "stove", inputs: [o], outputs: [new Item("burned", POT)], warn: true })
    return o
}
function sear(s, duration = 15) {
    s = s.tr(PAN)
    const o = new Item(`seared-${s.name}`, s.container)
    out({ action: "passive", duration, revert_duration: duration * 2, tile: "stove", inputs: [s], outputs: [o] })
    out({ action: "passive", duration: duration / 3, revert_duration: duration / 3, tile: "stove", inputs: [o], outputs: [new Item("burned", PAN)], warn: true })
    return o
}
function bake(s, duration = 25) {
    const o = s.container == "rolled-dough"
        ? new Item(s.name, new Item("pizza"))
        : new Item(`baked-${s.name}`, s.container)
    out({ action: "passive", duration, revert_duration: duration * 2, tile: "oven", inputs: [s], outputs: [o] })
    out({ action: "passive", duration: duration / 2, revert_duration: duration / 4, tile: "oven", inputs: [o], outputs: [new Item("burned")], warn: true })
    return o
}
function freeze(s) {
    const o = new Item(`frozen-${s.name}`, s.container)
    out({ action: "passive", duration: 25, tile: "freezer", inputs: [s], outputs: [o] })
    return o
}
function process(s) {
    const o = new Item(`processed-${s.name}`, s.container)
    out({ action: "passive", duration: 5, inputs: [s], outputs: [o] })
    return o
}
function container_add(base, add) {
    const o = new Item("!!!", base.container)
    out({ action: "instant", inputs: [base, add], outputs: [o, add.container] })
    return o
}
function combine(c, ...items) {
    if (items.length == 1) return items[0].tr(c)
    const open = items.map(i => (i.tr(c), [i.name]))
    const seen = new Set()
    let result
    while (1) {
        const cur = open.pop()
        if (!cur) break;
        for (const new_item of items) {
            if (cur.includes(new_item.name)) continue
            const rkey = cur.join(",") + "#" + new_item

            if (seen.has(rkey)) continue
            seen.add(rkey)

            const parts = [...cur, new_item.name]
            parts.sort()
            open.push(parts)
            const i = new Item(cur.join(","), c)
            const o = new Item(parts.join(","), c)
            if (parts.length == items.length) result = o
            out({
                action: "instant",
                inputs: [i, new_item],
                outputs: [o, new_item.container]
            })
        }
    }
    return result
}
function edible(group, ...items) {
    for (const i of items) {
        const info = auto_info(i.container?.name ?? i.name)
        out({ action: "demand", inputs: [i], outputs: info.dispose_self ? [] : [info.dispose_out ?? i.container], duration: 10, group })
    }
}
function either(a, b) {
    if (a.name != b.name) throw new Error("either options are named differently");
    if (a.container?.name != b.container?.name) throw new Error("either options are contained differently");
    return a
}
function sink_fill(c) {
    const o = new Item("water", c)
    out({ action: "active", inputs: [c], outputs: [o], tile: "sink", duration: 1 })
    return o
}
function group_hidden(i) {
    i.group_hidden = true
    return i
}

out({ action: "active", duration: 2, tile: "sink", inputs: [new Item("dirty-plate")], outputs: [PL] })

const tomato = crate("tomato")
const steak = crate("steak")
const flour = crate("flour")
const leek = crate("leek")
const rice = crate("rice")
const fish = crate("fish")
const coconut = crate("coconut")
const strawberry = crate("strawberry")
const cheese = crate("cheese")
const lettuce = crate("lettuce")
const mushroom = crate("mushroom")
const potato = crate("potato")

// Common renamed
const tomato_juice = process(tomato.tr(FP)).as("tomato-juice")
const patty = cut(steak).as("patty")
const dough = process(flour.tr(FP)).as("dough").tr()

// Pizza
const pizza_dough = roll(dough)
edible("pizza",
    bake(combine(pizza_dough, tomato_juice, cut(cheese), cut(mushroom))).tr_nested(PL),
    group_hidden(bake(combine(pizza_dough, tomato_juice, cut(cheese))).tr_nested(PL))
    // bake(combine(pizza_dough, patty, cut(cheese))).tr(PL),
    // bake(combine(pizza_dough, patty, leek)).tr(PL),
)

// Bowl
// edible(combine(PL, cook(rice.tr(POT)), cut(mushroom), cut(tomato)).as("rice-bowl"))

// Steak
const french_fries = deep_fry(cut(potato).tr(BA)).as("french-fries");
const bun = either(bake(dough).as("bun"), crate("bun"))
edible("steak",
    group_hidden(french_fries.tr(PL)),
    group_hidden(combine(PL, sear(steak))),
    combine(PL, sear(steak), french_fries),
    // combine(PL, sear(steak), cook(potato.tr(POT))),
    // combine(PL, sear(steak), sear(cut(mushroom))),
    // combine(PL, sear(steak), sear(cut(mushroom)), french_fries),
    // combine(PL, sear(steak), sear(cut(mushroom)), cook(potato.tr(POT))),
)

// Salad
edible("salad",
    combine(PL, cut(tomato), cut(lettuce)),
    group_hidden(combine(PL, cut(lettuce))),
)

// Burger
const sliced_bun = cut(bun, false, 1)
edible("burger",
    combine(PL, sliced_bun, sear(patty), cut(tomato), cut(lettuce)),
    combine(PL, sliced_bun, sear(patty), cut(cheese), cut(tomato)),
    group_hidden(combine(PL, sliced_bun, sear(patty), cut(cheese))),
    group_hidden(combine(PL, sliced_bun, sear(patty), cut(cheese), cut(lettuce))),
    group_hidden(combine(PL, sliced_bun, cut(cheese), cut(lettuce), cut(tomato))),
    group_hidden(combine(PL, sliced_bun, cut(lettuce), cut(tomato))),
    combine(PL, sliced_bun, cut(fish))
)

// Soup
const tomato_soup = cook(combine(POT, tomato_juice)).as("tomato-soup")
edible("soups",
    tomato_soup.tr(PL),
    cook(combine(POT, process(mushroom.tr(FP)))).as("mushroom-soup").tr(PL),
    cook(combine(POT, cheese, cut(leek))).as("cheese-leek-soup").tr(PL),
    // cook(combine(PL, patty, cut(potato), cut(leek))).as("stew").tr(PL)
)

// Noodles
const raw_noodles = either(cut(roll(dough)).as("noodles"), crate("noodles"))
const noodle = cook(raw_noodles.tr(POT))
edible("noodles",
    combine(PL, noodle, tomato_soup, cut(cheese)),
    group_hidden(combine(PL, noodle, tomato_soup)),
    // combine(PL, noodle, tomato_juice, cut(cheese), sear(patty)),
)

// Rice and nigiri
edible("nigiri",
    container_add(cut(fish), cook(rice.tr(POT))).as("nigiri").tr(PL),
    container_add(cook(rice.tr(POT)).tr(PL), cut(fish)).as("nigiri"),
    container_add(cut(fish).tr(PL), cook(rice.tr(POT))).as("nigiri"),
)

// coconut milk and strawberry puree
const strawberry_puree = process(strawberry.tr(FP)).as("strawberry-puree")
const milk = process(coconut.tr(FP)).as("milk")
const strawberry_shake = either(
    process(container_add(milk, strawberry).as("milk,strawberry")).as("strawberry-shake"),
    process(container_add(strawberry_puree, coconut).as("coconut,strawberry-puree")).as("strawberry-shake")
)

// Doughnut (Pfannkuchen)
// edible("doughnut", deep_fry(dough.tr(BA)).as("doughnut").tr(PL))

// Icecream
edible("icecream", freeze(strawberry_shake.tr(GL)).as("strawberry-icecream"))

// Mochi
const rice_flour = process(rice.tr(FP)).as("rice-flour")
const mochi_dough = cook(rice_flour.tr(POT), 5).as("mochi-dough")
const strawberry_mochi = container_add(strawberry, mochi_dough).as("strawberry-mochi")
edible("mochi", strawberry_mochi.tr(PL))

// Drinks
edible("drinks",
    strawberry_shake.tr(GL),
    tomato_juice.tr(GL),
    sink_fill(GL)
)

// Curry
const curry_with_rice = combine(PL, cook(rice.tr(POT)), cook(combine(POT, milk, cut(tomato), cut(leek))).as("curry"))
edible("curry", curry_with_rice)

auto_trash()
auto_burn()
finish()

