'use strict';
// VK preloader Lottie: a flat wooden Erudit tile ("Э" + score "8") that drops in
// under gravity, squashes on impact (convex cushion bulging out the left/right edges
// + a touch of skew), and springs back up — looping. A light "glint" is caught at the
// moment of the bounce. Pure 2D shapes (ddd:0). Glyphs are real LiberationSans
// outlines (Arial-metric, = the game's font stack); see extract.js -> glyphs.json.
const fs = require('fs');
const G = JSON.parse(fs.readFileSync(__dirname + '/glyphs.json', 'utf8'));

// ---- canvas / timing -------------------------------------------------------
const W = 96, H = 96, cx = 48;
const FR = 30, OP = 34;                      // ~1.13 s loop (dynamic, 25% faster)
// keyframe beats (frames): fast fall -> soft squash -> lift -> fast rise -> apex (no dwell)
const T_HIT = 13, T_PEAK = 18, T_LIFT = 19, T_REC = 28;

// ---- geometry --------------------------------------------------------------
const HW = 26, HH = 26, CR = 6;              // tile half-width / half-height / corner radius
const FLOOR = 90, APEX = 60;                 // bottom-centre screen-y at rest / at the top of the bounce

// ---- palette ---------------------------------------------------------------
const col = h => [parseInt(h.slice(1,3),16)/255, parseInt(h.slice(3,5),16)/255, parseInt(h.slice(5,7),16)/255];
const FACE = col('#D9B978');                 // wood face (flat)
const FACE_GLINT = col('#E7CD95');           // face flash caught on impact (gentle, not blinding)
const BORDER = col('#B49559');               // tile rim: a touch darker than the face, reads on any bg
const BLACK = col('#1A1A1A');
const BLACK_LIT = col('#421A0B');            // glyph warms to brown-red on the glint
const lerp = (a, b, t) => a.map((v, i) => v + (b[i] - v) * t);

// ---- property / easing helpers ---------------------------------------------
const still = v => ({ a: 0, k: v });
const EI = { o: { x: [0.82], y: [0] }, i: { x: [1], y: [1] } };  // strong accelerate (gravity fall)
const EO = { o: { x: [0], y: [0] }, i: { x: [0.18], y: [1] } };  // strong decelerate (rise to apex)
const ES = { o: { x: [0.42], y: [0] }, i: { x: [0.58], y: [1] } }; // soft/slow (the cushion)
function anim(samples) {                      // samples: {t, v, e?} ; e = easing toward the NEXT key
  return { a: 1, k: samples.map((s, idx) => {
    const kf = { t: s.t, s: s.v };
    if (idx < samples.length - 1) { const e = s.e || ES; kf.o = e.o; kf.i = e.i; }
    return kf;
  }) };
}
const animColor = s => anim(s.map(x => ({ t: x.t, v: [...x.v, 1], e: x.e })));

// ---- shape helpers ---------------------------------------------------------
const tr = () => ({ ty: 'tr', p: still([0,0]), a: still([0,0]), s: still([100,100]), r: still(0), o: still(100) });
const grp = (items, nm) => ({ ty: 'gr', it: [...items, tr()], nm });
const fill = c => ({ ty: 'fl', c, o: still(100), r: 1, bm: 0 });
const stroke = (c,w) => ({ ty: 'st', c: still(c), o: still(100), w: still(w), lc: 2, lj: 2, ml: 4, bm: 0 });
function glyph(gd, hpx, px, py, bold, colour, nm) {  // font glyph -> filled+stroked group
  const sc = hpx / gd.bbox.h;
  const bcx = gd.bbox.x + gd.bbox.w / 2, bcy = gd.bbox.y + gd.bbox.h / 2;
  const shapes = gd.contours.map(ct => ({
    ty: 'sh', d: 1, ks: still({
      i: ct.i.map(p => [p[0]*sc, p[1]*sc]), o: ct.o.map(p => [p[0]*sc, p[1]*sc]),
      v: ct.v.map(p => [(p[0]-bcx)*sc + px, (p[1]-bcy)*sc + py]), c: true,
    }),
  }));
  return grp([...shapes, fill(colour), stroke(BLACK, bold)], nm);
}

// Sample the rest rounded-rect outline (clockwise): fine corner arcs + one mid point
// per straight edge, so a smooth interpolant reproduces it cleanly.
function arc(ccx, ccy, a0, a1, n) {
  const out = [];
  for (let i = 0; i < n; i++) { const a = (a0 + (a1 - a0) * i / (n - 1)) * Math.PI / 180; out.push([ccx + CR*Math.cos(a), ccy + CR*Math.sin(a)]); }
  return out;
}
const REST = (() => {
  const NC = 7, yi = HH - CR, xi = HW - CR;
  const C = [ arc(xi,-yi,-90,0,NC), arc(xi,yi,0,90,NC), arc(-xi,yi,90,180,NC), arc(-xi,-yi,180,270,NC) ];
  const pts = [];
  for (let k = 0; k < 4; k++) {
    pts.push(...C[k]);
    const a = C[k][NC-1], b = C[(k+1)%4][0];
    pts.push([(a[0]+b[0])/2, (a[1]+b[1])/2]);   // straight-edge mid point (keeps the edge straight)
  }
  return pts;
})();
// The whole left/right contour (corners + side) bows out by one smooth barrel profile;
// Catmull-Rom tangents (from neighbours) make every junction C1-smooth -> no kink, the
// corners follow the cushion and the cushion melts into the corners, bulged or not.
function facePath(b) {
  const f = y => b * (1 - (y/HH) * (y/HH));                       // 0 at top/bottom, max at the middle
  const V = REST.map(([x, y]) => [x + Math.sign(x) * f(y), y]);   // push the sides out, top/bottom stay
  const n = V.length, I = [], O = [];
  for (let k = 0; k < n; k++) {
    const p0 = V[(k-1+n)%n], p1 = V[k], p2 = V[(k+1)%n];
    let dx = p2[0]-p0[0], dy = p2[1]-p0[1];                       // tangent direction (neighbours)
    const dl = Math.hypot(dx, dy) || 1; dx /= dl; dy /= dl;
    const ln = Math.hypot(p2[0]-p1[0], p2[1]-p1[1]) / 3;          // handles ~ 1/3 of the adjacent chord
    const lp = Math.hypot(p1[0]-p0[0], p1[1]-p0[1]) / 3;          // -> chordal spline, no overshoot/facets
    O.push([dx*ln, dy*ln]); I.push([-dx*lp, -dy*lp]);
  }
  return { i: I, o: O, v: V, c: true };
}
const facePathKeys = samples => ({ a: 1, k: samples.map((s, idx) => {
  const kf = { t: s.t, s: [facePath(s.b)] };
  if (idx < samples.length - 1) { const e = s.e || ES; kf.o = e.o; kf.i = e.i; }
  return kf;
}) });

// ---- animation tracks ------------------------------------------------------
// bottom-centre screen-y (the tile rests/squashes on its bottom edge)
const posY = anim([
  { t: 0,      v: [cx, APEX, 0],  e: EI },           // apex -> immediately falls (no dwell)
  { t: T_HIT,  v: [cx, FLOOR, 0], e: ES },           // FAST fall (accelerate) -> floor
  { t: T_LIFT, v: [cx, FLOOR, 0], e: EO },           // sit on the floor while the cushion absorbs
  { t: OP,     v: [cx, APEX, 0] },                   // FAST rise (decelerate) -> apex = loop start
]);
// scale about the bottom anchor: stretch while falling, gentle/slow cushion squash on impact
const scl = anim([
  { t: 0,        v: [100, 100, 100], e: ES },
  { t: T_HIT-5,  v: [95, 106, 100], e: ES },         // stretch while falling fast
  { t: T_HIT,    v: [104, 95, 100], e: ES },         // touch down
  { t: T_PEAK,   v: [112, 86, 100], e: ES },         // soft squash peak (gentler, less plче)
  { t: T_REC,    v: [100, 100, 100], e: ES },        // slow cushion release -> soft landing
  { t: OP,       v: [100, 100, 100] },
]);
// a touch of skew through the squash (organic deform), back to 0
const skw = anim([
  { t: T_HIT, v: [0], e: ES }, { t: T_PEAK, v: [4], e: ES }, { t: T_REC, v: [0] }, { t: OP, v: [0] },
]);
// left/right cushion bulge: 0 -> gentle peak -> 0 (never inward)
const bulge = facePathKeys([
  { t: T_HIT, b: 0, e: ES }, { t: T_PEAK, b: 4, e: ES }, { t: T_REC, b: 0 }, { t: OP, b: 0 },
]);
// glint: face + glyph catch the light at the bounce
const faceCol = animColor([
  { t: T_HIT-2, v: FACE, e: ES }, { t: T_PEAK, v: FACE_GLINT, e: ES }, { t: T_REC, v: FACE }, { t: OP, v: FACE },
]);
const glyphCol = animColor([
  { t: T_HIT-2, v: BLACK, e: ES }, { t: T_PEAK, v: BLACK_LIT, e: ES }, { t: T_REC, v: BLACK }, { t: OP, v: BLACK },
]);

// ---- layer (face + glyphs share the bounce/squash transform) ---------------
const faceShape = grp([ { ty: 'sh', d: 1, ks: bulge }, fill(faceCol), stroke(BORDER, 1.8) ], 'face');
const glyphE = glyph(G.E,  32, 0, -1, 1.0, glyphCol, 'E');                 // centred Э
const glyph8 = glyph(G.D8, 8.5, HW-6.5, HH-7.5, 0.5, glyphCol, 'score');   // 8 in the corner

const tile = {
  ddd: 0, ind: 1, ty: 4, nm: 'tile', sr: 1,
  ks: { o: still(100), r: still(0), p: posY, a: still([0, HH, 0]), s: scl, sk: skw, sa: still(0) },
  ao: 0, shapes: [ glyph8, glyphE, faceShape ], ip: 0, op: OP, st: 0, bm: 0,
};

const root = {
  v: '5.7.4', fr: FR, ip: 0, op: OP, w: W, h: H, nm: 'erudit-vk-loader', ddd: 0,
  assets: [], layers: [ tile ], markers: [],
};

const out = process.argv[2] || 'erudit.json';
const round = (k, v) => (typeof v === 'number' ? Math.round(v * 100) / 100 : v);
fs.writeFileSync(out, JSON.stringify(root, round));
console.log('wrote', out, fs.statSync(out).size, 'bytes');
