import UpgradeComponent from "@/components/features/Upgrade.vue";
import {
    CoercableComponent,
    Component,
    findFeatures,
    getUniqueID,
    makePersistent,
    Persistent,
    PersistentState,
    Replace,
    setDefault,
    StyleValue,
    Visibility
} from "@/features/feature";
import { Resource } from "@/features/resource";
import { GenericLayer } from "@/game/layers";
import Decimal, { DecimalSource } from "@/util/bignum";
import { isFunction } from "@/util/common";
import {
    Computable,
    GetComputableType,
    GetComputableTypeWithDefault,
    processComputable,
    ProcessedComputable
} from "@/util/computed";
import { createProxy } from "@/util/proxies";
import { computed, Ref, unref } from "vue";

export const UpgradeType = Symbol("Upgrade");

export interface UpgradeOptions {
    visibility?: Computable<Visibility>;
    classes?: Computable<Record<string, boolean>>;
    style?: Computable<StyleValue>;
    display?: Computable<
        | CoercableComponent
        | {
              title?: CoercableComponent;
              description: CoercableComponent;
              effectDisplay?: CoercableComponent;
          }
    >;
    mark?: Computable<boolean | string>;
    cost?: Computable<DecimalSource>;
    resource?: Computable<Resource>;
    canPurchase?: Computable<boolean>;
    onPurchase?: VoidFunction;
}

interface BaseUpgrade extends Persistent<boolean> {
    id: string;
    bought: Ref<boolean>;
    canAfford: Ref<boolean>;
    purchase: VoidFunction;
    type: typeof UpgradeType;
    [Component]: typeof UpgradeComponent;
}

export type Upgrade<T extends UpgradeOptions> = Replace<
    T & BaseUpgrade,
    {
        visibility: GetComputableTypeWithDefault<T["visibility"], Visibility.Visible>;
        classes: GetComputableType<T["classes"]>;
        style: GetComputableType<T["style"]>;
        display: GetComputableType<T["display"]>;
        mark: GetComputableType<T["mark"]>;
        cost: GetComputableType<T["cost"]>;
        resource: GetComputableType<T["resource"]>;
        canPurchase: GetComputableTypeWithDefault<T["canPurchase"], Ref<boolean>>;
    }
>;

export type GenericUpgrade = Replace<
    Upgrade<UpgradeOptions>,
    {
        visibility: ProcessedComputable<Visibility>;
        canPurchase: ProcessedComputable<boolean>;
    }
>;

export function createUpgrade<T extends UpgradeOptions>(
    options: T & ThisType<Upgrade<T>>
): Upgrade<T> {
    const upgrade: T & Partial<BaseUpgrade> = options;
    makePersistent<boolean>(upgrade, false);
    upgrade.id = getUniqueID("upgrade-");
    upgrade.type = UpgradeType;
    upgrade[Component] = UpgradeComponent;

    if (upgrade.canPurchase == null && (upgrade.resource == null || upgrade.cost == null)) {
        console.warn(
            "Error: can't create upgrade without a canPurchase property or a resource and cost property",
            upgrade
        );
    }

    upgrade.bought = upgrade[PersistentState];
    if (upgrade.canAfford == null) {
        upgrade.canAfford = computed(
            () =>
                proxy.resource != null &&
                proxy.cost != null &&
                Decimal.gte(unref<Resource>(proxy.resource).value, unref(proxy.cost))
        );
    }
    if (upgrade.canPurchase == null) {
        upgrade.canPurchase = computed(() => unref(proxy.canAfford) && !unref(proxy.bought));
    }
    upgrade.purchase = function () {
        if (!unref(proxy.canPurchase)) {
            return;
        }
        if (proxy.resource != null && proxy.cost != null) {
            proxy.resource.value = Decimal.sub(
                unref<Resource>(proxy.resource).value,
                unref(proxy.cost)
            );
        }
        proxy[PersistentState].value = true;
        proxy.onPurchase?.();
    };

    processComputable(upgrade as T, "visibility");
    setDefault(upgrade, "visibility", Visibility.Visible);
    processComputable(upgrade as T, "classes");
    processComputable(upgrade as T, "style");
    processComputable(upgrade as T, "display");
    processComputable(upgrade as T, "mark");
    processComputable(upgrade as T, "cost");
    processComputable(upgrade as T, "resource");
    processComputable(upgrade as T, "canPurchase");

    const proxy = createProxy(upgrade as unknown as Upgrade<T>);
    return proxy;
}

export function setupAutoPurchase(
    layer: GenericLayer,
    autoActive: Computable<boolean>,
    upgrades: GenericUpgrade[] = []
): void {
    upgrades = upgrades || findFeatures(layer, UpgradeType);
    const isAutoActive = isFunction(autoActive) ? computed(autoActive) : autoActive;
    layer.on("update", () => {
        if (unref(isAutoActive)) {
            upgrades.forEach(upgrade => upgrade.purchase());
        }
    });
}