import { Ok, Error } from "./gleam.mjs";

class PaintCanvas extends HTMLElement {
  // Open an issue if you are in need of any other attributes :)
  static observedAttributes = ["width", "height", "style", "picture"];

  constructor() {
    super();
    // Create a canvas
    this.canvas = document.createElement("canvas");
    const style = document.createElement("style");
    style.textContent = `
      :host {
        display: inline-block;
      }
    `;
    this.shadow = this.attachShadow({ mode: "open" });
    this.shadow.appendChild(style);
    this.shadow.appendChild(this.canvas);
    this.ctx = this.canvas.getContext("2d");
  }

  attributeChangedCallback(name, _oldValue, newValue) {
    if (name === "picture") {
      this.picture = newValue;
      return;
    } else if (name === "width") {
      this.width = newValue;
    } else if (name === "height") {
      this.height = newValue;
    }
  }

  drawPicture() {
    if (!this.pictureString) {
      return;
    }

    this.ctx.reset();
    const display =
      window.PAINT_STATE[
        "display_on_rendering_context_with_default_drawing_state"
      ];

    display(this.pictureString, this.ctx);
  }

  set picture(value) {
    this.pictureString = value;
    this.drawPicture();
  }

  set width(value) {
    this.canvas.width = value;
    this.drawPicture();
  }

  set height(value) {
    this.canvas.height = value;
    this.drawPicture();
  }

  get width() {
    return this.canvas.width;
  }

  get height() {
    return this.canvas.height;
  }
}

export function define_web_component() {
  window.customElements.define("paint-canvas", PaintCanvas);
}

export function get_rendering_context(selector) {
  // TODO: Handle the case where the canvas element is not found.
  return document.querySelector(selector).getContext("2d");
}

export function setup_request_animation_frame(callback) {
  window.requestAnimationFrame((time) => {
    callback(time);
  });
}

export function setup_input_handler(event_name, callback) {
  window.addEventListener(event_name, callback);
}

export function get_key_code(event) {
  return event.keyCode;
}

export function set_global(state, id) {
  if (typeof window.PAINT_STATE == "undefined") {
    window.PAINT_STATE = {};
  }
  window.PAINT_STATE[id] = state;
}

export function get_global(id) {
  if (!window.PAINT_STATE) {
    return new Error(undefined);
  }
  if (!(id in window.PAINT_STATE)) {
    return new Error(undefined);
  }
  return new Ok(window.PAINT_STATE[id]);
}

export function get_width(ctx) {
  return ctx.canvas.clientWidth;
}

export function get_height(ctx) {
  return ctx.canvas.clientHeight;
}

// Based on https://stackoverflow.com/questions/17130395/real-mouse-position-in-canvas
export function mouse_pos(ctx, event) {
  // Calculate the scaling of the canvas vs its content
  const rect = ctx.canvas.getBoundingClientRect();
  const scaleX = ctx.canvas.width / rect.width;
  const scaleY = ctx.canvas.height / rect.height;

  return [
    (event.clientX - rect.left) * scaleX,
    (event.clientY - rect.top) * scaleY,
  ];
}

// if check_pressed is true, the function will return true if the button was pressed
// if check_pressed is false, the function will return true if the button was released
export function check_mouse_button(
  event,
  previous_event,
  button_index,
  check_pressed,
) {
  let previous_buttons = previous_event?.buttons ?? 0;
  let current_buttons = event.buttons;

  // ~001 &&
  //  011
  //  -----
  //  010 found the newly pressed!
  //
  //   011 &&
  //  ~001
  //   -----
  //   010 found the newly released!
  if (check_pressed) {
    previous_buttons = ~previous_buttons;
  } else {
    current_buttons = ~current_buttons;
  }

  let button = previous_buttons & current_buttons & (1 << button_index);
  return !!button;
}

export function reset(ctx) {
  ctx.reset();
}

export function arc(ctx, radius, start, end, fill, stroke) {
  ctx.beginPath();
  ctx.arc(0, 0, radius, start, end);
  if (fill) {
    ctx.fill();
  }
  if (stroke) {
    ctx.stroke();
  }
}

export function polygon(ctx, points, closed, fill, stroke) {
  ctx.beginPath();
  ctx.moveTo(0, 0);
  let started = false;
  for (const point of points) {
    let x = point[0];
    let y = point[1];
    if (started) {
      ctx.lineTo(x, y);
    } else {
      ctx.moveTo(x, y);
      started = true;
    }
  }

  if (closed) {
    ctx.closePath();
  }

  if (fill && closed) {
    ctx.fill();
  }

  if (stroke) {
    ctx.stroke();
  }
}

export function text(ctx, text, style) {
  ctx.font = style;
  ctx.fillText(text, 0, 0);
}

export function save(ctx) {
  ctx.save();
}

export function restore(ctx) {
  ctx.restore();
}

export function set_fill_colour(ctx, css_colour) {
  ctx.fillStyle = css_colour;
}

export function set_stroke_color(ctx, css_color) {
  ctx.strokeStyle = css_color;
}

export function set_line_width(ctx, width) {
  ctx.lineWidth = width;
}

export function translate(ctx, x, y) {
  ctx.translate(x, y);
}

export function scale(ctx, x, y) {
  ctx.scale(x, y);
}

export function rotate(ctx, radians) {
  ctx.rotate(radians);
}

export function reset_transform(ctx) {
  ctx.resetTransform();
}

export function draw_image(ctx, image, width_px, height_px) {
  ctx.drawImage(image, 0, 0, width_px, height_px);
}

export function image_from_query(selector) {
  return document.querySelector(selector);
}

export function image_from_src(src) {
  const image = new Image();
  image.src = src;
  return image;
}

export function on_image_load(image, callback) {
  if (image.complete) {
    callback();
  } else {
    image.addEventListener("load", callback);
  }
}

export function set_image_smoothing_enabled(ctx, value) {
  ctx.imageSmoothingEnabled = value;
}
