<script setup lang="ts">
import { ref, onMounted, useTemplateRef, computed } from "vue";
import LucideIcon from "./components/LucideIcon.vue";

enum Filter {
    None = "None",
    Gray = "Gray",
    Sepia = "Sepia",
    Invert = "Invert",
}
enum PAPosition {
    None = "None",
    BL = "Bottom Left",
    BC = "Bottom Center",
    BR = "Bottom Right",
    TL = "Top Left",
    TC = "Top Center",
    TR = "Top Right",
}

const PA_RATIO = 404 / 646;

const visible = ref<boolean>(false);
const srcData = ref<string | null>(null);
const srcWidth = ref<number>(0);
const srcHeight = ref<number>(0);
const srcLoaded = ref<boolean>(false);

const centerX = ref<number>(0.5);
const centerY = ref<number>(0.5);
const zoom = ref<number>(1);
const filter = ref<Filter>(Filter.None);
const paPos = ref<PAPosition>(PAPosition.BR);
const paScale = ref<number>(0.2);
const paMargin = ref<number>(0.05);

const targetSize = computed<number>(() => {
    if (!srcWidth.value || !srcHeight.value) {
        return 100;
    }
    return Math.min(srcWidth.value, srcHeight.value);
});

const input = useTemplateRef<HTMLInputElement>("input");
const image = useTemplateRef<HTMLImageElement>("image");
const parentalAdvisory = useTemplateRef<HTMLImageElement>("parentalAdvisory");
const canvas = useTemplateRef<HTMLCanvasElement>("canvas");

function openImage() {
    if (!input.value?.files?.length) {
        return;
    }
    const reader = new FileReader();
    reader.onload = () => {
        image.value.src = reader.result as string;
    };
    reader.readAsDataURL(input.value.files[0]);
}

function imageOnLoad() {
    srcWidth.value = image.value?.width ?? 0;
    srcHeight.value = image.value?.height ?? 0;
    if (!srcWidth.value || !srcHeight.value) {
        return;
    }
    srcLoaded.value = true;
    setTimeout(draw);
}

function randomElement(items) {
    return items[Math.floor(Math.random() * items.length)];
}

function randomize() {
    centerX.value = Math.random();
    centerY.value = Math.random();
    zoom.value = 1 + Math.random() * 3;
    filter.value = randomElement(Object.values(Filter));
    paPos.value = randomElement(Object.values(PAPosition));
    paScale.value = 0.1 + Math.random() * 0.2;
    paMargin.value = Math.random() * 0.1;
    draw();
}

function reset() {
    centerX.value = 0.5;
    centerY.value = 0.5;
    zoom.value = 1;
    filter.value = Filter.None;
    paPos.value = PAPosition.BR;
    paScale.value = 0.2;
    paMargin.value = 0.05;
    draw();
}

function newImage() {
    srcLoaded.value = false;
    reset();
}

function download() {
    const link = document.createElement("a");
    link.download = "coverify.png";
    link.href = canvas.value.toDataURL();
    link.click();
}

function applyFilter(f: Filter, r: number, g: number, b: number) {
    switch (f) {
        case Filter.Gray:
            const gray = 0.21 * r + 0.72 * g + 0.07 * b;
            return [gray, gray, gray];
        case Filter.Invert:
            return [255 - r, 255 - g, 255 - b];
        case Filter.Sepia:
            return [
                Math.min(255, Math.round(r * 0.393 + g * 0.769 + b * 0.189)),
                Math.min(255, Math.round(r * 0.349 + g * 0.686 + b * 0.168)),
                Math.min(255, Math.round(r * 0.272 + g * 0.534 + b * 0.131)),
            ];
    }
    return [r, g, b];
}

function draw() {
    const ctx = canvas.value?.getContext("2d");
    if (!ctx) {
        return;
    }
    canvas.value.width = targetSize.value;
    canvas.value.height = targetSize.value;
    ctx.clearRect(0, 0, targetSize.value, targetSize.value);

    const imgWidth = srcWidth.value * zoom.value;
    const imgHeight = srcHeight.value * zoom.value;
    const dx = (targetSize.value - imgWidth) * centerX.value;
    const dy = (targetSize.value - imgHeight) * centerY.value;
    ctx.drawImage(image.value, dx, dy, imgWidth, imgHeight);

    const imageData = ctx.getImageData(
        0,
        0,
        targetSize.value,
        targetSize.value,
    );
    const pixelData = imageData.data;
    for (let i = 0; i < pixelData.length; i += 4) {
        const r1 = pixelData[i];
        const g1 = pixelData[i + 1];
        const b1 = pixelData[i + 2];

        const [r2, g2, b2] = applyFilter(filter.value, r1, g1, b1);

        pixelData[i] = r2;
        pixelData[i + 1] = g2;
        pixelData[i + 2] = b2;
        pixelData[i + 3] = 255;
    }

    ctx.putImageData(imageData, 0, 0);

    if (paPos.value !== PAPosition.None) {
        const paWidth = targetSize.value * paScale.value;
        const paHeight = paWidth * PA_RATIO;
        let padx = paMargin.value * targetSize.value;
        let pady = paMargin.value * targetSize.value;
        if (
            paPos.value === PAPosition.BC ||
            paPos.value === PAPosition.BL ||
            paPos.value === PAPosition.BR
        ) {
            pady = targetSize.value - paHeight - pady;
        }
        if (paPos.value === PAPosition.BR || paPos.value === PAPosition.TR) {
            padx = targetSize.value - paWidth - padx;
        } else if (
            paPos.value === PAPosition.BC ||
            paPos.value === PAPosition.TC
        ) {
            padx = (targetSize.value - paWidth) * 0.5;
        }
        ctx.drawImage(parentalAdvisory.value, padx, pady, paWidth, paHeight);
    }
}

onMounted(() => {
    setTimeout(() => {
        visible.value = true;
    });
});
</script>

<template>
    <main :style="{ display: visible ? 'inherit' : 'none' }">
        <h1>
            <LucideIcon name="disc-3" />
            Coverify
        </h1>
        <p><i>Create album covers from mundane photos</i></p>
        <img
            ref="image"
            style="display: none"
            :src="srcData"
            @load="imageOnLoad"
        />
        <img
            ref="parentalAdvisory"
            style="display: none"
            src="./parental_advisory.png"
        />
        <input
            v-if="!srcLoaded"
            ref="input"
            type="file"
            accept="image/*"
            @change="openImage"
        />
        <canvas
            v-show="srcLoaded"
            ref="canvas"
            :width="targetSize"
            :height="targetSize"
        ></canvas>
        <br />
        <template v-if="srcLoaded">
            <div class="buttons">
                <button @click="randomize">
                    <LucideIcon name="dices" /> Randomize
                </button>
                <button @click="reset">
                    <LucideIcon name="rotate-ccw" /> Reset
                </button>
                <button @click="newImage">
                    <LucideIcon name="trash-2" /> New picture
                </button>
                <button @click="download">
                    <LucideIcon name="arrow-down-to-line" /> Download
                </button>
            </div>
            <br />
            <table class="config">
                <colgroup>
                    <col style="width: 25%" />
                    <col />
                    <col style="width: 10%" />
                </colgroup>
                <tr>
                    <td><label for="center-x">Center X:</label></td>
                    <td>
                        <input
                            id="center-x"
                            v-model="centerX"
                            type="range"
                            min="0"
                            max="1"
                            step="0.01"
                            @input="draw"
                        />
                    </td>
                    <td>{{ (centerX * 100).toFixed(0) }}%</td>
                </tr>
                <tr>
                    <td><label for="center-y">Center Y:</label></td>
                    <td>
                        <input
                            id="center-y"
                            v-model="centerY"
                            type="range"
                            min="0"
                            max="1"
                            step="0.01"
                            @input="draw"
                        />
                    </td>
                    <td>{{ (centerY * 100).toFixed(0) }}%</td>
                </tr>
                <tr>
                    <td><label for="zoom">Zoom:</label></td>
                    <td>
                        <input
                            id="zoom"
                            v-model="zoom"
                            type="range"
                            min="1"
                            max="4"
                            step="0.01"
                            @input="draw"
                        />
                    </td>
                    <td>{{ (zoom * 100).toFixed(0) }}%</td>
                </tr>
                <tr>
                    <td><label for="filter">Filter:</label></td>
                    <td>
                        <select id="filter" v-model="filter" @change="draw">
                            <option
                                v-for="value in Object.values(Filter)"
                                :key="`filter-${value}`"
                            >
                                {{ value }}
                            </option>
                        </select>
                    </td>
                    <td></td>
                </tr>
                <tr>
                    <td><label for="pa-pos">Sticker Position:</label></td>
                    <td>
                        <select id="pa-pos" v-model="paPos" @change="draw">
                            <option
                                v-for="value in Object.values(PAPosition)"
                                :key="`pa-pos-${value}`"
                            >
                                {{ value }}
                            </option>
                        </select>
                    </td>
                    <td></td>
                </tr>
                <tr v-if="paPos !== PAPosition.None">
                    <td><label for="pa-scale">Sticker Scale:</label></td>
                    <td>
                        <input
                            id="pa-scale"
                            v-model="paScale"
                            type="range"
                            min="0.10"
                            max="0.30"
                            step="0.01"
                            @input="draw"
                        />
                    </td>
                    <td>{{ (paScale * 100).toFixed(0) }}%</td>
                </tr>
                <tr v-if="paPos !== PAPosition.None">
                    <td><label for="pa-margin">Sticker Margin:</label></td>
                    <td>
                        <input
                            id="pa-margin"
                            v-model="paMargin"
                            type="range"
                            min="0"
                            max="0.1"
                            step="0.01"
                            @input="draw"
                        />
                    </td>
                    <td>{{ (paMargin * 100).toFixed(0) }}%</td>
                </tr>
            </table>
        </template>
        <br />
        <hr />
        <small class="footer">
            <LucideIcon name="at-sign" />
            <a href="https://github.com/klemek" target="_blank">klemek</a>
            -
            <LucideIcon name="github" />
            <a href="https://github.com/klemek/coverify" target="_blank"
                >Repository</a
            >
            - 2025
        </small>
    </main>
</template>

<style scoped>
h1 {
    margin-top: 0;
}

.buttons {
    display: flex;
    width: 100%;
}

.buttons > button {
    flex: 1;
}

canvas {
    width: min(40vh, 100%);
    margin: auto;
}

select,
input {
    width: 100%;
}

.button:hover {
    background-color: var(--background-secondary);
}

.footer {
    opacity: 50%;
}
</style>
