import type { Device } from "@pkg/logic/domains/device/data";
import type { CreateLink, LinkWithDevice } from "@pkg/logic/domains/link/data";
import type { SupportedApp } from "@pkg/logic/domains/supported-app/data";
import {
    listLinksSQ,
    createLinkSC,
    revokeLinkSC,
    deleteLinkSC,
    assignDeviceSC,
} from "./link.remote";
import { listDevicesSQ } from "../device/device.remote";
import { listSupportedAppsSQ } from "../supported-app/supported-app.remote";
import { toast } from "svelte-sonner";

type DeviceOption = Pick<Device, "id" | "title" | "host" | "status" | "inUse">;
type SupportedAppOption = Pick<SupportedApp, "id" | "title" | "packageName">;
type CreateLinkInput = Omit<CreateLink, "token">;

class LinkViewModel {
    links = $state<LinkWithDevice[]>([]);
    availableDevices = $state<DeviceOption[]>([]);
    availableSupportedApps = $state<SupportedAppOption[]>([]);
    loading = $state(false);
    creating = $state(false);
    deletingId = $state<number | null>(null);
    revokingId = $state<number | null>(null);
    showCreateDialog = $state(false);

    /**
     * SvelteKit's query() caches the RemoteQuery object in an internal query_map.
     * Subsequent calls to listLinksSQ() return the same cached Query with stale data.
     * We must call refresh() on the cached query to force a fresh server request.
     */
    private _linksQuery: ReturnType<typeof listLinksSQ> | null = null;

    async fetchLinks() {
        this.loading = true;
        try {
            const query = listLinksSQ();

            // On subsequent calls the query is cached and stale — refresh it
            if (query.ready) {
                await query.refresh();
            }

            // After refresh, read .current; on first load, await the initial fetch
            const result = query.ready ? query.current : await query;
            this._linksQuery = query;

            if (result?.error || !result?.data) {
                toast.error(
                    result?.error?.message || "Failed to fetch links",
                    {
                        description:
                            result?.error?.description || "Please try again",
                    },
                );
                return;
            }
            this.links = result.data as LinkWithDevice[];
        } catch (error) {
            toast.error("Failed to fetch links", {
                description:
                    error instanceof Error
                        ? error.message
                        : "Please try again",
            });
        } finally {
            this.loading = false;
        }
    }

    async fetchDevicesForSelect() {
        try {
            const result = await listDevicesSQ();
            if (result?.data) {
                this.availableDevices = (result.data as Device[]).map((d) => ({
                    id: d.id,
                    title: d.title,
                    host: d.host,
                    status: d.status,
                    inUse: d.inUse,
                }));
            }
        } catch {
            // Non-critical - select will just be empty
        }
    }

    async fetchSupportedAppsForSelect() {
        try {
            const result = await listSupportedAppsSQ();
            if (result?.data) {
                this.availableSupportedApps = result.data as SupportedAppOption[];
            }
        } catch {
            // Non-critical - select will just be empty
        }
    }

    async createLink(data: CreateLinkInput): Promise<boolean> {
        this.creating = true;
        try {
            const result = await createLinkSC(data);
            if (result?.error) {
                toast.error(result.error.message || "Failed to create link", {
                    description:
                        result.error.description || "Please try again",
                });
                return false;
            }
            toast.success("Link created");
            this.showCreateDialog = false;
            await this.fetchLinks();
            return true;
        } catch (error) {
            toast.error("Failed to create link", {
                description:
                    error instanceof Error
                        ? error.message
                        : "Please try again",
            });
            return false;
        } finally {
            this.creating = false;
        }
    }

    async revokeLink(id: number) {
        this.revokingId = id;
        try {
            const result = await revokeLinkSC({ id });
            if (result?.error) {
                toast.error(result.error.message || "Failed to revoke link", {
                    description:
                        result.error.description || "Please try again",
                });
                return;
            }
            toast.success("Link revoked");
            await this.fetchLinks();
        } catch (error) {
            toast.error("Failed to revoke link", {
                description:
                    error instanceof Error
                        ? error.message
                        : "Please try again",
            });
        } finally {
            this.revokingId = null;
        }
    }

    async deleteLink(id: number) {
        this.deletingId = id;
        try {
            const result = await deleteLinkSC({ id });
            if (result?.error) {
                toast.error(result.error.message || "Failed to delete link", {
                    description:
                        result.error.description || "Please try again",
                });
                return;
            }
            toast.success("Link deleted");
            this.links = this.links.filter((l) => l.id !== id);
        } catch (error) {
            toast.error("Failed to delete link", {
                description:
                    error instanceof Error
                        ? error.message
                        : "Please try again",
            });
        } finally {
            this.deletingId = null;
        }
    }

    async assignDevice(linkId: number, deviceId: number | null) {
        try {
            const result = await assignDeviceSC({ id: linkId, deviceId });
            if (result?.error) {
                toast.error(
                    result.error.message || "Failed to assign device",
                    {
                        description:
                            result.error.description || "Please try again",
                    },
                );
                return;
            }
            toast.success(deviceId ? "Device assigned" : "Device unassigned");
            await this.fetchLinks();
        } catch (error) {
            toast.error("Failed to assign device", {
                description:
                    error instanceof Error
                        ? error.message
                        : "Please try again",
            });
        }
    }

    getDeviceName(deviceId: number | null): string {
        if (!deviceId) return "—";
        const device = this.availableDevices.find((d) => d.id === deviceId);
        return device ? device.title : `Device #${deviceId}`;
    }

    getSupportedAppName(supportedAppId: number): string {
        const app = this.availableSupportedApps.find((d) => d.id === supportedAppId);
        return app ? app.title : `App #${supportedAppId}`;
    }
}

export const linkVM = new LinkViewModel();
