const elemHeader = document.querySelector<HTMLParagraphElement>('p.header')!
const elemDescription = document.querySelector<HTMLParagraphElement>('p.description')!
const elemRedirect = document.querySelector<HTMLAnchorElement>('a.redirect')!

function createImage(src: string): HTMLImageElement {
    const i = document.createElement('img')
    i.src = src
    return i
}

const slides = new Array<{
    image: HTMLImageElement
    url?: string | null
    tooltip?: string
    header: string
    description: string
}>({
    image: createImage('/index/slide-1.gif'),
    header: 'Tech, Chunibyo & Other Delusions',
    url: '/secret-picross.html',
    tooltip: 'Picross',
    description:
        'Welcome to my personal homepage, use the buttons at the top of the page to visit other sections of the site.',
})

const CODE_VERTEX = `
    attribute vec2 p;
    varying vec2 uv;
    void main(){
        uv = p * 0.5 + 0.5;
        gl_Position = vec4(p, 0, 1);
    }
`

const CODE_SHADER = `
    precision highp float;
    varying vec2 uv;
    uniform vec2 res;
    uniform sampler2D texA;
    uniform sampler2D texB;
    uniform float transition, degauss, time, barrel, scan, mask, chroma, bloom, vig, noise, flicker, jitter, bright, sat, corner;

    float rand(vec2 co){
        return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453);
    }

    vec2 barrelD(vec2 u, float k) {
        vec2 c = u - 0.5;
        return u + c * dot(c, c) * k;
    }

    vec3 sampleChroma(sampler2D t, vec2 u) {
        float ca = chroma * (1. + dot(u - .5, u - .5) * 2.);
        float r = texture2D(t, barrelD(u + vec2(ca, 0), barrel)).r;
        float g = texture2D(t, barrelD(u, barrel)).g;
        float b = texture2D(t, barrelD(u - vec2(ca, 0), barrel)).b;
        return vec3(r, g, b);
    }

    vec3 phosphorBloom(sampler2D t, vec2 u) {
        vec3 glow = vec3(0.0);
        float w = 0.0;

        // two rings — tight inner glow + wide outer halo
        for (int x = -4; x <= 4; x++) {
            for (int y = -4; y <= 4; y++) {
                vec2 off = vec2(float(x), float(y)) / res * 3.5;
                vec3 s = texture2D(t, barrelD(u + off, barrel)).rgb;

                // only bright pixels contribute — threshold at 0.6 luma
                float lum = dot(s, vec3(0.299, 0.587, 0.114));
                float thresh = max(lum - 0.55, 0.0) * 2.5;
                s *= thresh;

                float wt = exp(-dot(vec2(x, y), vec2(x, y)) * 0.18);
                glow += s * wt;
                w += wt;
            }
        }
        glow /= w;

        // wide halo pass at 3x the offset
        vec3 halo = vec3(0.0);
        float hw = 0.0;
        for (int x = -3; x <= 3; x++) {
            for (int y = -3; y <= 3; y++) {
                vec2 off = vec2(float(x), float(y)) / res * 10.0;
                vec3 s = texture2D(t, barrelD(u + off, barrel)).rgb;
                float lum = dot(s, vec3(0.299, 0.587, 0.114));
                float thresh = max(lum - 0.7, 0.0) * 4.0;
                s *= thresh;
                float wt = exp(-dot(vec2(x, y), vec2(x, y)) * 0.25);
                halo += s * wt;
                hw += wt;
            }
        }
        halo /= hw;

        // phosphor tint — warm yellow-green inner, cooler blue outer halo
        glow *= vec3(1.1, 1.05, 0.85);
        halo *= vec3(0.85, 0.95, 1.15);

        return glow * bloom + halo * bloom * 0.4;
    }

    vec3 shadowMask(vec2 u) {
        float px = mod(u.x * res.x, 3.);
        vec3 m = vec3(step(px, 1.), step(1., px) * step(px, 2.), step(2., px));
        return mix(vec3(1.), m * 1.6 + .15, mask * .5);
    }

    float scanlineW(vec2 u) {
        return mix(1., max(0., sin(u.y * res.y * 3.14159)) * .5 + .5, scan);
    }

    void main() {
        vec2 u = uv;
        u.x += sin(u.y * 200. + time * 3.) * (jitter * .5) + sin(u.y * 50. + time * 7.) * jitter;

        // degauss
        vec2 centered = u - 0.5;
        float dist = length(centered);
        float wavefront = degauss * 0.9;
        float ring = exp(-pow((dist - wavefront) * 12.0, 2.0));
        float push = ring * 0.08 * sin(degauss * 3.14159);
        u += normalize(centered + 0.0001) * push;

        vec2 bd = barrelD(u, barrel);
        if (bd.x < 0. || bd.x > 1. || bd.y < 0. || bd.y > 1.) {
            gl_FragColor = vec4(0, 0, 0, 1);
            return;
        }

        vec2 cr = vec2(corner * res.y / res.x, corner);
        vec2 e = smoothstep(vec2(0.), cr, bd) * smoothstep(vec2(1.), vec2(1.) - cr, bd);
        if (e.x * e.y < .01) {
            gl_FragColor = vec4(0, 0, 0, 1);
            return;
        }

        // chroma boost at ring
        float ca_boost = chroma + ring * 0.03 * sin(degauss * 3.14159);

        // old texture
        float rA = texture2D(texA,  barrelD(u + vec2(ca_boost, 0.), barrel)).r;
        float gA = texture2D(texA,  barrelD(u, barrel)).g;
        float bA = texture2D(texA,  barrelD(u - vec2(ca_boost, 0.), barrel)).b;

        // new texture
        float rB = texture2D(texB, barrelD(u + vec2(ca_boost, 0.), barrel)).r;
        float gB = texture2D(texB, barrelD(u, barrel)).g;
        float bB = texture2D(texB, barrelD(u - vec2(ca_boost, 0.), barrel)).b;

        // wipe: pixels closer to center than wavefront show new texture
        float wipe = smoothstep(wavefront - 0.05, wavefront + 0.05, dist);
        vec3 col = mix(vec3(rB, gB, bB), vec3(rA, gA, bA), wipe);

        // rainbow tint at ring
        col += ring * 0.4 * vec3(
            sin(degauss * 6.28 + 0.0),
            sin(degauss * 6.28 + 2.09),
            sin(degauss * 6.28 + 4.18)
        ) * sin(degauss * 3.14159);

        vec3 bl = mix(phosphorBloom(texB, u), phosphorBloom(texA, u), wipe);
        col += bl;


        float lum = dot(col, vec3(.299, .587, .114));
        col = mix(col, bl, bloom * (lum * lum * .5 + .1));
        col *= shadowMask(bd); col *= scanlineW(bd);

        float v = 1. - dot(bd - .5, bd - .5) * vig * 2.; col *= clamp(v, 0., 1.);
        float fl = 1. - flicker * .5 + flicker * sin(time * 73. + bd.y * 100.) * .5; col *= fl;
        col += (rand(bd + fract(time * .1)) - .5) * noise;
        col *= bright;

        float l2 = dot(col, vec3(.299, .587, .114)); col = mix(vec3(l2), col, sat);
        col = pow(max(col, vec3(0.)), vec3(1. / 2.2));
        col *= e.x * e.y;
        gl_FragColor = vec4(clamp(col, 0., 1.), 0);
    }
`

const canvas = document.querySelector<HTMLCanvasElement>('canvas')!
const W = canvas.clientWidth
const H = canvas.clientHeight
let degauss = 0,
    noise = 0,
    slideIndex = -1,
    slideCounter = 0

canvas.width = W
canvas.height = H

const gl = canvas.getContext('webgl')!
const program = gl.createProgram()

function createShader(shaderType: number, shaderCode: string) {
    const shader = gl.createShader(shaderType)!
    gl.shaderSource(shader, shaderCode)
    gl.compileShader(shader)
    return shader
}

gl.attachShader(program, createShader(gl.VERTEX_SHADER, CODE_VERTEX))
gl.attachShader(program, createShader(gl.FRAGMENT_SHADER, CODE_SHADER))
gl.linkProgram(program)
gl.useProgram(program)

const bf = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, bf)
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW)

const ap = gl.getAttribLocation(program, 'p')
gl.enableVertexAttribArray(ap)
gl.vertexAttribPointer(ap, 2, gl.FLOAT, false, 0, 0)

const U = {
    transition: gl.getUniformLocation(program, 'transition'),
    degauss: /**/ gl.getUniformLocation(program, 'degauss'),
    time: /*---*/ gl.getUniformLocation(program, 'time'),
    res: /*----*/ gl.getUniformLocation(program, 'res'),
    texA: /*---*/ gl.getUniformLocation(program, 'texA'),
    texB: /*---*/ gl.getUniformLocation(program, 'texB'),
    barrel: /*-*/ gl.getUniformLocation(program, 'barrel'),
    scan: /*---*/ gl.getUniformLocation(program, 'scan'),
    mask: /*---*/ gl.getUniformLocation(program, 'mask'),
    chroma: /*-*/ gl.getUniformLocation(program, 'chroma'),
    bloom: /*--*/ gl.getUniformLocation(program, 'bloom'),
    vig: /*----*/ gl.getUniformLocation(program, 'vig'),
    noise: /*--*/ gl.getUniformLocation(program, 'noise'),
    flicker: /**/ gl.getUniformLocation(program, 'flicker'),
    jitter: /*-*/ gl.getUniformLocation(program, 'jitter'),
    bright: /*-*/ gl.getUniformLocation(program, 'bright'),
    sat: /*----*/ gl.getUniformLocation(program, 'sat'),
    corner: /*-*/ gl.getUniformLocation(program, 'corner'),
}

const textureA = gl.createTexture()
const textureCanvasA = document.createElement('canvas')
const textureContextA = textureCanvasA.getContext('2d')!
{
    textureCanvasA.width = W
    textureCanvasA.height = H
    gl.bindTexture(gl.TEXTURE_2D, textureA)
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
}

const textureB = gl.createTexture()
const textureCanvasB = document.createElement('canvas')
const textureContextB = textureCanvasB.getContext('2d')!
{
    textureCanvasB.width = W
    textureCanvasB.height = H
    gl.bindTexture(gl.TEXTURE_2D, textureB)
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
}

// function renderSMPTE() {
//     const canvas = textureCanvasA
//     const ctx = textureContextA

//     canvas.width = W
//     canvas.height = H

//     const bars = [
//         ['#c0c0c0', '#c0c000', '#00c0c0', '#00c000', '#c000c0', '#c00000', '#0000c0'],
//         ['#0000c0', '#101010', '#c000c0', '#101010', '#00c0c0', '#101010', '#c0c0c0'],
//         ['#001b4b', '#ffffff', '#2e006a', '#0b0b0b', '#0b0b0b', '#0b0b0b'],
//     ]
//     let bw = W / 7
//     bars[0].forEach((c, i) => {
//         ctx.fillStyle = c
//         ctx.fillRect(i * bw, 0, bw, H * 0.675)
//     })
//     bars[1].forEach((c, i) => {
//         ctx.fillStyle = c
//         ctx.fillRect(i * bw, H * 0.675, bw, H * 0.125)
//     })
//     bw = W / 6
//     bars[2].forEach((c, i) => {
//         ctx.fillStyle = c
//         ctx.fillRect(i * bw, H * 0.75, bw, H * 0.25)
//     })
//     ctx.fillStyle = '#fff'
//     ctx.fillRect(W * 0.625, H * 0.8, W * 0.25, H * 0.12)

//     ctx.font = 'bold 24px monospace'
//     ctx.fillStyle = '#000'
//     ctx.fillText('PLEASE STAND BY', W * 0.65, H * 0.875)

//     gl.activeTexture(gl.TEXTURE0)
//     gl.bindTexture(gl.TEXTURE_2D, textureA)
//     gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true)
//     gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas)
// }

function renderFrame(t: number) {
    gl.uniform1f(U.time, t)
    gl.uniform2f(U.res, W, H)
    gl.uniform1f(U.degauss, degauss)
    gl.uniform1i(U.texA, 0)
    gl.uniform1i(U.texB, 1)
    gl.uniform1f(U.barrel, /*-*/ 0.25)
    gl.uniform1f(U.scan, /*---*/ 0.25)
    gl.uniform1f(U.mask, /*---*/ 1)
    gl.uniform1f(U.chroma, /*-*/ 0.0025)
    gl.uniform1f(U.bloom, /*--*/ 0.5)
    gl.uniform1f(U.vig, /*----*/ 1)
    gl.uniform1f(U.noise, /*--*/ noise)
    gl.uniform1f(U.flicker, /**/ 0.045)
    gl.uniform1f(U.jitter, /*-*/ 0.00075)
    gl.uniform1f(U.bright, /*-*/ 0.8)
    gl.uniform1f(U.sat, /*----*/ 1.0)
    gl.uniform1f(U.corner, /*-*/ 0.1)

    gl.viewport(0, 0, W, H)
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4)
    requestAnimationFrame(renderFrame)
}

async function nextSlide(init = false) {
    const duration = 1000
    const nextIndex = slideCounter++ % slides.length
    if (nextIndex === slideIndex) return
    slideIndex = nextIndex

    const next = slides[nextIndex]
    elemHeader.textContent = next.header
    elemDescription.textContent = next.description

    if (next.url) {
        elemRedirect.href = next.url
        elemRedirect.style.opacity = '1'
        elemRedirect.style.pointerEvents = 'all'
    } else {
        elemRedirect.style.opacity = '0'
        elemRedirect.style.pointerEvents = 'none'
    }
    elemRedirect.textContent = `Discover ${next.tooltip || 'More'} >>>`

    function tween(from: number, to: number, ms: number, fn: (v: number) => void) {
        return new Promise<void>((resolve) => {
            const start = performance.now()
            function tick() {
                const t = Math.min((performance.now() - start) / ms, 1)
                const e = t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t // easing
                fn(from + (to - from) * e)
                if (t < 1) requestAnimationFrame(tick)
                else resolve()
            }
            requestAnimationFrame(tick)
        })
    }

    textureContextB.drawImage(next.image!, 0, 0, W, H)
    gl.activeTexture(gl.TEXTURE1)
    gl.bindTexture(gl.TEXTURE_2D, textureB)
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true)
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, textureCanvasB)

    if (init) {
        tween(0, 0.02, duration, (t) => (noise = t))
        setInterval(nextSlide, duration + 8000)
    }
    await tween(0, 1, duration, (t) => (degauss = t))

    textureContextA.drawImage(next.image!, 0, 0, W, H)
    gl.activeTexture(gl.TEXTURE0)
    gl.bindTexture(gl.TEXTURE_2D, textureA)
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true)
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, textureCanvasA)

    degauss = 0
}

// Startup Sequence (Blank Screen)
slides[0].image.onload = () => {
    nextSlide(true)
    requestAnimationFrame(renderFrame)
}

gl.activeTexture(gl.TEXTURE0)
gl.bindTexture(gl.TEXTURE_2D, textureA)
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true)
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas)
