---
import ContactCard from "../components/ContactCard.astro";

import crypto from "crypto";

// Store contact info in a dictionary
const contactInfo = {
  email: {
    value: "me@samvid.dev",
    link: "mailto:me@samvid.dev",
  },
  signal: {
    value: "samvidk.85",
    link: "https://signal.me/#eu/eFj_OirfNWU7IkLM_0rHhusUBVuQQaoPGYH_1LrhdmSKuW-TIflE6ovHlxGkB-Vq",
  },
  matrix: {
    value: "@theamazing0:tchncs.de",
    link: "https://matrix.to/#/@theamazing0:tchncs.de",
  },
};

const encryptedContactInfo = JSON.stringify(
  encryptString(JSON.stringify(contactInfo)),
);

function encryptString(input: string, difficultyPrefix = "0000") {
  const challenge = crypto.randomBytes(8).toString("hex");
  let nonce = 0;
  let hash;

  while (true) {
    const candidate = challenge + nonce;
    hash = crypto.createHash("sha256").update(candidate).digest("hex");
    if (hash.startsWith(difficultyPrefix)) break;
    nonce++;
  }

  const key = crypto
    .createHash("sha256")
    .update(challenge + nonce)
    .digest()
    .slice(0, 32);

  const iv = crypto.randomBytes(12);
  const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
  const encrypted = Buffer.concat([
    cipher.update(input, "utf8"),
    cipher.final(),
  ]);
  const tag = cipher.getAuthTag();

  return {
    ciphertext: Buffer.concat([encrypted, tag]).toString("base64"),
    iv: iv.toString("base64"),
    challenge,
    difficulty: difficultyPrefix,
  };
}
---

<div id="prompt-div">
  <p class="subtitle">
    In order to protect against bots, this website uses a lightweight proof of
    work algorithm in your browser to decrypt my contact information.
  </p>
  <p class="subtitle">
    This keeps spam bots from finding my info while keeping it easy for humans
    to access it.
  </p>
  <p class="subtitle" style="font-weight: 700;" id="progress-label"></p>
  <a class="button" id="decode-btn">Decode Info</a>
</div>
<div id="details-div" data-encrypted={encryptedContactInfo} hidden>
  <p class="subtitle">I would love to talk to you!</p>
  <p class="subtitle">
    The following methods support <span style="font-weight: 700;"
      >privacy-friendly</span
    > and <span style="font-weight: 700;">secure</span> communication:
  </p>
  <div class="card-container">
    <ContactCard medium="Email" pgp />
    <ContactCard medium="Signal" />
    <ContactCard medium="Matrix" />
  </div>
</div>

<style>
  .subtitle {
    color: #c3d6b2;
  }
  .card-container {
    display: flex;
    flex-wrap: wrap;
  }
  .button {
    display: inline-block;
    padding: 0.5rem 0.75rem;
    font-size: 0.8rem;
    background-color: #e6fbd4;
    color: #222b1a;
    font-weight: 700;
    border-radius: 10px;
    text-decoration: none;
    cursor: pointer;
    transition:
      background 0.2s,
      color 0.2s;
  }
  .button:hover {
    background-color: #c3d6b2;
  }

  @media only screen and (min-width: 769px) {
    .subtitle {
      max-width: 65%;
    }
  }
</style>

<script>
  async function sha256(str: string) {
    const buf = new TextEncoder().encode(str);
    const hash = await crypto.subtle.digest("SHA-256", buf);
    return Array.from(new Uint8Array(hash))
      .map((b) => b.toString(16).padStart(2, "0"))
      .join("");
  }

  async function findNonce(challenge: string, difficultyPrefix = "00000") {
    let nonce = 0;
    const startTime = performance.now();
    let lastUpdateTime = startTime;

    while (true) {
      const hash = await sha256(challenge + nonce);
      if (hash.startsWith(difficultyPrefix)) {
        return nonce.toString();
      }
      nonce++;

      const currentTime = performance.now();
      if (currentTime - lastUpdateTime >= 1000) {
        const elapsedTime = (currentTime - startTime) / 1000;
        const attemptsPerSecond = Math.floor(nonce / elapsedTime);
        const estimatedTotalAttempts = Math.pow(16, difficultyPrefix.length);
        const progress = Math.min(
          (nonce / estimatedTotalAttempts) * 100,
          100,
        ).toFixed(2);
        const eta = (
          (estimatedTotalAttempts - nonce) /
          attemptsPerSecond
        ).toFixed(2);

        const progressLabel = document.querySelector("#progress-label");
        if (progressLabel) {
          progressLabel.innerHTML = `~${progress}% done, ${attemptsPerSecond}/s, ETA: ${eta}s`;
        }

        lastUpdateTime = currentTime;
      }
    }
  }

  async function decryptData(e: Event) {
    e.preventDefault();

    const detailsDiv = document.getElementById("details-div");
    if (detailsDiv) {
      const encryptedDetails = detailsDiv.dataset.encrypted;
      if (encryptedDetails) {
        const { ciphertext, iv, challenge, difficulty } =
          JSON.parse(encryptedDetails);

        const nonce = await findNonce(challenge, difficulty);
        const keyMaterial = await sha256(challenge + nonce);
        const matchResult = keyMaterial.match(/.{1,2}/g);
        if (!matchResult) {
          throw new Error("Failed to match key material");
        }
        const keyBytes = new Uint8Array(
          matchResult.map((b) => parseInt(b, 16)),
        );
        const cryptoKey = await crypto.subtle.importKey(
          "raw",
          keyBytes,
          { name: "AES-GCM" },
          false,
          ["decrypt"],
        );
        const cipherBytes = Uint8Array.from(atob(ciphertext), (c) =>
          c.charCodeAt(0),
        );
        const decrypted = await crypto.subtle.decrypt(
          {
            name: "AES-GCM",
            iv: Uint8Array.from(atob(iv), (c) => c.charCodeAt(0)),
          },
          cryptoKey,
          cipherBytes,
        );
        const decryptedData = JSON.parse(new TextDecoder().decode(decrypted));

        document.querySelectorAll("[data-contact-card]").forEach((card) => {
          const mediumLabel = card.querySelector("[data-medium]");
          if (mediumLabel) {
            const medium = mediumLabel.innerHTML.toLowerCase();
            if (medium) {
              const idLabel = card.querySelector("[data-id]");
              if (idLabel) {
                idLabel.innerHTML = decryptedData[medium]["value"];
              }
              const linkBtn = card.querySelector("[data-link]");
              if (linkBtn) {
                linkBtn.setAttribute("href", decryptedData[medium]["link"]);
              }
            }
          }
        });
        const promptDiv = document.getElementById("prompt-div");
        if (promptDiv) {
          promptDiv.hidden = true;
        }
        const detailsDiv = document.getElementById("details-div");
        if (detailsDiv) {
          detailsDiv.hidden = false;
        }
      }
    }
  }

  document.addEventListener("astro:page-load", () => {
    const decodeBtn = document.getElementById("decode-btn");
    if (decodeBtn) {
      decodeBtn.addEventListener("click", decryptData);
    }
  });
</script>
