import Foundation

/// Builds the alien scene. It's rendered in a transparent WKWebView so the whole
/// thing is CSS-animated SVG — easy to retheme, and it films well.
///
/// Beat sheet (7.0s total):
///   0.0–1.0  saucer glides in from off-screen left
///   1.05     alien's head pops up out of the dome
///   1.35     speech bubble appears, text types in
///   5.30     bubble collapses
///   5.50     head ducks back down
///   5.9–7.0  saucer banks up and zips off right
enum Alien {
    /// Total scene length. Swift closes the window just after this.
    static let sceneDuration: TimeInterval = 7.0

    /// - Parameters:
    ///   - scale: overall size multiplier. 1.0 is a modest HUD; the default 1.4 reads
    ///     properly in a screen recording that gets cropped to 9:16.
    ///   - palette: which brand colourway to wear.
    static func html(eyebrow: String,
                     line: String,
                     scale: Double = 1.4,
                     palette: Palette = .launchpad) -> String {
        let scaleSafe = min(3.0, max(0.5, scale))
        let eyebrowSafe = escape(eyebrow)
        // The line is injected as a JS string and set via textContent, so it needs
        // JS escaping only — HTML-escaping it here would show literal "&amp;".
        let lineSafe = jsString(line)

        let p = palette

        return #"""
        <!doctype html>
        <meta charset="utf-8">
        <style>
          :root {
            --skin-1: \#(p.skin.0);
            --skin-2: \#(p.skin.1);
            --skin-3: \#(p.skin.2);
            --hull-1: \#(p.hull.0);
            --hull-2: \#(p.hull.1);
            --hull-3: \#(p.hull.2);
            --glow:    \#(p.glow);
            --accent:  \#(p.accent);
            --ink:     \#(p.ink);
            --outline: \#(p.outline);
            --text:    #f6f1ff;
            --scale:   \#(String(format: "%.2f", scaleSafe));
          }

          html, body {
            margin: 0; padding: 0; height: 100%; width: 100%;
            background: transparent;
            overflow: hidden;
            -webkit-user-select: none;
            font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", sans-serif;
          }

          /* The ship parks slightly right of centre and a third down the screen. */
          .stage {
            position: absolute;
            left: 50%; top: 34%;
            transform: translate(-50%, -50%) scale(var(--scale));
          }

          .flight { animation: fly 7s cubic-bezier(.33,.02,.28,1) forwards; }
          .bob    { animation: bob 2.4s 1s ease-in-out infinite; }
          .craft  { position: relative; width: 300px; height: 190px; }

          @keyframes fly {
            0%   { transform: translate(-58vw, 4vh) scale(.72); opacity: 0; }
            6%   { opacity: 1; }
            15%  { transform: translate(0, 0) scale(1); }
            84%  { transform: translate(0, 0) scale(1); opacity: 1; }
            100% { transform: translate(62vw, -9vh) scale(.68); opacity: 0; }
          }
          @keyframes bob {
            0%, 100% { transform: translateY(0); }
            50%      { transform: translateY(-9px); }
          }

          /* ---------- saucer ---------- */
          .saucer {
            position: absolute; bottom: 0; left: 0;
            width: 300px; height: 132px;
            /* Palette glow for the look, plus a dark shadow so the hull separates
               from white app windows underneath. */
            filter: drop-shadow(0 10px 26px \#(p.glow)80)
                    drop-shadow(0 4px 10px rgba(12,8,31,.45));
          }

          /* Light beam under the craft. */
          .beam {
            position: absolute; left: 50%; bottom: 6px;
            width: 120px; height: 150px;
            transform: translateX(-50%);
            background: linear-gradient(to bottom, \#(p.glow)55, \#(p.glow)00 78%);
            clip-path: polygon(34% 0, 66% 0, 100% 100%, 0 100%);
            animation: beam 2.6s 1.2s ease-in-out infinite;
          }
          @keyframes beam {
            0%, 100% { opacity: .35; }
            50%      { opacity: .8; }
          }

          /* ---------- alien ---------- */
          .alien-wrap {
            position: absolute; left: 96px; bottom: 74px;
            width: 108px; height: 104px;
            transform: translateY(34px);
            animation: pop .55s 1.05s cubic-bezier(.2,1.5,.4,1) forwards,
                       duck .45s 5.5s ease-in forwards;
          }
          @keyframes pop  { to { transform: translateY(-6px); } }
          @keyframes duck { to { transform: translateY(36px); opacity: .15; } }

          .tilt { animation: tilt 3s 1.7s ease-in-out infinite; transform-origin: 50% 92%; }
          @keyframes tilt {
            0%, 100% { transform: rotate(-4deg); }
            50%      { transform: rotate(5deg); }
          }

          .antenna { transform-box: fill-box; transform-origin: 50% 100%;
                     animation: wiggle 1.5s 1.6s ease-in-out infinite; }
          @keyframes wiggle {
            0%, 100% { transform: rotate(-11deg); }
            50%      { transform: rotate(12deg); }
          }

          .blip { animation: blip 1.5s 1.6s ease-in-out infinite; }
          @keyframes blip {
            0%, 100% { opacity: .55; r: 5; }
            50%      { opacity: 1;   r: 7; }
          }

          .eye { transform-box: fill-box; transform-origin: center;
                 animation: blink 4.2s 2.2s ease-in-out infinite; }
          @keyframes blink {
            0%, 46%, 52%, 100% { transform: scaleY(1); }
            49%                { transform: scaleY(.08); }
          }

          /* ---------- speech bubble ---------- */
          .bubble {
            position: absolute;
            left: 296px; bottom: 96px;
            min-width: 210px; max-width: 400px;
            padding: 15px 20px 16px;
            border-radius: 17px;
            background: \#(p.ink)f5;
            border: 1px solid \#(p.accent)b0;
            box-shadow: 0 18px 44px rgba(0,0,0,.5),
                        0 0 34px \#(p.accent)55;
            opacity: 0;
            transform: translateX(-14px) scale(.86);
            transform-origin: 0 70%;
            animation: bubbleIn .42s 1.35s cubic-bezier(.2,1.4,.4,1) forwards,
                       bubbleOut .3s 5.3s ease-in forwards;
          }
          @keyframes bubbleIn  { to { opacity: 1; transform: translateX(0) scale(1); } }
          @keyframes bubbleOut { to { opacity: 0; transform: translateX(-10px) scale(.9); } }

          /* Tail pointing back at the alien. */
          .bubble::before {
            content: ""; position: absolute;
            left: -7px; bottom: 20px;
            width: 14px; height: 14px;
            background: \#(p.ink)f5;
            border-left: 1px solid \#(p.accent)b0;
            border-bottom: 1px solid \#(p.accent)b0;
            transform: rotate(45deg);
          }

          /* Dismiss button, hanging off the bubble's top-right corner.
             It's drawn here but clicked through a small catcher window the app
             places on top of it — the overlay itself stays click-through. */
          .close {
            position: absolute; top: -13px; right: -13px;
            width: 29px; height: 29px;
            border-radius: 50%;
            background: \#(p.ink);
            border: 1.5px solid \#(p.accent)e6;
            box-shadow: 0 2px 10px rgba(0,0,0,.55), 0 0 16px \#(p.accent)66;
            display: flex; align-items: center; justify-content: center;
          }
          .close svg { width: 13px; height: 13px; display: block; }
          /* Set by the app while the pointer is over the catcher window. */
          .close.hot {
            filter: brightness(1.5);
            transform: scale(1.12);
          }

          .eyebrow {
            font-size: 10.5px; font-weight: 700; letter-spacing: .17em;
            color: var(--accent); margin-bottom: 6px;
            text-transform: uppercase;
          }
          .line {
            font-size: 17.5px; font-weight: 600; line-height: 1.32;
            color: var(--text);
            white-space: pre-wrap;
          }
          /* Blinking cursor while the line types itself in. */
          .caret {
            display: inline-block; width: 2px; height: 1.05em;
            margin-left: 2px; vertical-align: -2px;
            background: var(--accent);
            animation: caret .7s steps(1) infinite;
          }
          @keyframes caret { 0%, 49% { opacity: 1 } 50%, 100% { opacity: 0 } }
        </style>

        <div class="stage">
          <div class="flight"><div class="bob"><div class="craft">

            <div class="beam"></div>

            <div class="alien-wrap"><svg viewBox="0 0 108 104" class="tilt">
              <defs>
                <linearGradient id="skin" x1="0" y1="0" x2="0" y2="1">
                  <stop offset="0"   stop-color="\#(p.skin.0)"/>
                  <stop offset=".55" stop-color="\#(p.skin.1)"/>
                  <stop offset="1"   stop-color="\#(p.skin.2)"/>
                </linearGradient>
                <radialGradient id="glow" cx=".5" cy=".5" r=".5">
                  <stop offset="0" stop-color="\#(p.glow)" stop-opacity=".85"/>
                  <stop offset="1" stop-color="\#(p.glow)" stop-opacity="0"/>
                </radialGradient>
              </defs>

              <!-- antenna -->
              <g class="antenna">
                <path d="M54 30 C53 19 57 13 62 7"
                      stroke="\#(p.skin.1)" stroke-width="4.5" fill="none"
                      stroke-linecap="round"/>
                <circle cx="62" cy="7" r="14" fill="url(#glow)"/>
                <circle class="blip" cx="62" cy="7" r="6" fill="\#(p.glow)"/>
              </g>

              <!-- head. The dark stroke keeps it readable against white apps. -->
              <ellipse cx="54" cy="58" rx="34" ry="31" fill="url(#skin)"
                       stroke="\#(p.outline)" stroke-width="2"/>
              <!-- cheek sheen -->
              <ellipse cx="41" cy="46" rx="11" ry="7" fill="#fff" opacity=".2"/>

              <!-- eyes -->
              <ellipse class="eye" cx="42" cy="57" rx="8.5" ry="11" fill="\#(p.ink)"/>
              <ellipse class="eye" cx="67" cy="57" rx="8.5" ry="11" fill="\#(p.ink)"/>
              <circle cx="44.5" cy="52.5" r="2.9" fill="\#(p.glow)"/>
              <circle cx="69.5" cy="52.5" r="2.9" fill="\#(p.glow)"/>

              <!-- little smile -->
              <path d="M47 74 Q54 80 61 74" stroke="\#(p.ink)" stroke-width="3"
                    fill="none" stroke-linecap="round" opacity=".8"/>
            </svg></div>

            <svg class="saucer" viewBox="0 0 300 132">
              <defs>
                <linearGradient id="hull" x1="0" y1="0" x2="1" y2="1">
                  <stop offset="0"   stop-color="\#(p.hull.0)"/>
                  <stop offset=".52" stop-color="\#(p.hull.1)"/>
                  <stop offset="1"   stop-color="\#(p.hull.2)"/>
                </linearGradient>
                <linearGradient id="dome" x1="0" y1="0" x2="0" y2="1">
                  <stop offset="0" stop-color="#ffffff" stop-opacity=".42"/>
                  <stop offset="1" stop-color="\#(p.glow)" stop-opacity=".14"/>
                </linearGradient>
              </defs>

              <!-- glass dome behind the alien -->
              <path d="M92 74 A58 50 0 0 1 208 74 Z" fill="url(#dome)"
                    stroke="rgba(240,250,255,.8)" stroke-width="2.2"/>
              <!-- specular streak on the glass -->
              <path d="M112 66 A44 38 0 0 1 132 36" stroke="rgba(255,255,255,.6)"
                    stroke-width="4" fill="none" stroke-linecap="round"/>
              <!-- hull -->
              <ellipse cx="150" cy="78" rx="146" ry="30" fill="url(#hull)"/>
              <ellipse cx="150" cy="72" rx="146" ry="26" fill="\#(p.ink)" opacity=".55"/>
              <ellipse cx="150" cy="70" rx="140" ry="22" fill="url(#hull)"/>
              <!-- rim highlight -->
              <ellipse cx="150" cy="68" rx="140" ry="20" fill="none"
                       stroke="rgba(255,255,255,.3)" stroke-width="1.4"/>
              <!-- running lights -->
              <circle cx="42"  cy="82" r="5.5" fill="\#(p.glow)"/>
              <circle cx="96"  cy="90" r="5.5" fill="\#(p.skin.0)"/>
              <circle cx="150" cy="93" r="5.5" fill="\#(p.glow)"/>
              <circle cx="204" cy="90" r="5.5" fill="\#(p.skin.0)"/>
              <circle cx="258" cy="82" r="5.5" fill="\#(p.glow)"/>
            </svg>

            <div class="bubble" id="bubble">
              <div class="close" id="closebtn">
                <svg viewBox="0 0 24 24" fill="none">
                  <path d="M7 7 L17 17 M17 7 L7 17" stroke="\#(p.accent)"
                        stroke-width="3" stroke-linecap="round"/>
                </svg>
              </div>
              <div class="eyebrow">\#(eyebrowSafe)</div>
              <div class="line"><span id="typed"></span><span class="caret"></span></div>
            </div>

          </div></div></div>
        </div>

        <script>
          function post(msg) {
            try { window.webkit.messageHandlers.hud.postMessage(msg); } catch (e) {}
          }

          // Type the line in, character by character, once the bubble has popped.
          var full = "\#(lineSafe)";
          var el = document.getElementById("typed");
          var i = 0;
          setTimeout(function tick() {
            if (i <= full.length) {
              el.textContent = full.slice(0, i++);
              setTimeout(tick, 17);
            }
          }, 1560);

          // Hover feedback, driven by the catcher window over the X.
          window.hudSetHot = function (on) {
            document.getElementById("closebtn").classList.toggle("hot", !!on);
          };

          // Once the bubble has settled, tell the app where the X ended up so it can
          // put its click-catcher exactly there.
          setTimeout(function () {
            var r = document.getElementById("closebtn").getBoundingClientRect();
            post({ t: "closeRect", x: r.left, y: r.top, w: r.width, h: r.height });
          }, 1850);

          // The bubble starts fading at 5.3s — retire the catcher with it.
          setTimeout(function () { post({ t: "closeGone" }); }, 5300);

          // Tell the app the scene is over so it can tear the window down.
          setTimeout(function () { post({ t: "done" }); },
                     \#(Int(sceneDuration * 1000)));
        </script>
        """#
    }

    private static func escape(_ s: String) -> String {
        s.replacingOccurrences(of: "&", with: "&amp;")
            .replacingOccurrences(of: "<", with: "&lt;")
            .replacingOccurrences(of: ">", with: "&gt;")
            .replacingOccurrences(of: "\"", with: "&quot;")
    }

    /// The typed line goes through a JS string literal as well as HTML.
    private static func jsString(_ s: String) -> String {
        s.replacingOccurrences(of: "\\", with: "\\\\")
            .replacingOccurrences(of: "\"", with: "\\\"")
            .replacingOccurrences(of: "\n", with: " ")
            .replacingOccurrences(of: "\r", with: "")
    }
}
