import * as $list from "../gleam_stdlib/gleam/list.mjs";
import * as $paint from "../paint/paint.mjs";
import { request_animation_frame, get_mouse_position, is_mouse_pressed } from "./browser_ffi.mjs";
import { Ok, toList, prepend as listPrepend, CustomType as $CustomType, isEqual } from "./gleam.mjs";
import * as $hud from "./hud.mjs";
import { CrossHair, Health, Hud, Score } from "./hud.mjs";
import * as $physics from "./physics.mjs";
import { Gravity, Physics, Vec2 } from "./physics.mjs";
import * as $rendering from "./rendering.mjs";
import * as $target from "./target.mjs";
import { Diamond, Lucy, Rocket } from "./target.mjs";
import * as $types from "./types.mjs";
import { GameOver, MainMenu, Playing, PlayingState } from "./types.mjs";

export { is_mouse_pressed, request_animation_frame };

export class TargetClickEffect extends $CustomType {
  constructor(score_change, health_change) {
    super();
    this.score_change = score_change;
    this.health_change = health_change;
  }
}
export const TargetClickEffect$TargetClickEffect = (score_change, health_change) =>
  new TargetClickEffect(score_change, health_change);
export const TargetClickEffect$isTargetClickEffect = (value) =>
  value instanceof TargetClickEffect;
export const TargetClickEffect$TargetClickEffect$score_change = (value) =>
  value.score_change;
export const TargetClickEffect$TargetClickEffect$0 = (value) =>
  value.score_change;
export const TargetClickEffect$TargetClickEffect$health_change = (value) =>
  value.health_change;
export const TargetClickEffect$TargetClickEffect$1 = (value) =>
  value.health_change;

export class TargetFilterResult extends $CustomType {
  constructor(remaining_targets, health_loss) {
    super();
    this.remaining_targets = remaining_targets;
    this.health_loss = health_loss;
  }
}
export const TargetFilterResult$TargetFilterResult = (remaining_targets, health_loss) =>
  new TargetFilterResult(remaining_targets, health_loss);
export const TargetFilterResult$isTargetFilterResult = (value) =>
  value instanceof TargetFilterResult;
export const TargetFilterResult$TargetFilterResult$remaining_targets = (value) =>
  value.remaining_targets;
export const TargetFilterResult$TargetFilterResult$0 = (value) =>
  value.remaining_targets;
export const TargetFilterResult$TargetFilterResult$health_loss = (value) =>
  value.health_loss;
export const TargetFilterResult$TargetFilterResult$1 = (value) =>
  value.health_loss;

export class SpawnResult extends $CustomType {
  constructor(targets, spawn_timer) {
    super();
    this.targets = targets;
    this.spawn_timer = spawn_timer;
  }
}
export const SpawnResult$SpawnResult = (targets, spawn_timer) =>
  new SpawnResult(targets, spawn_timer);
export const SpawnResult$isSpawnResult = (value) =>
  value instanceof SpawnResult;
export const SpawnResult$SpawnResult$targets = (value) => value.targets;
export const SpawnResult$SpawnResult$0 = (value) => value.targets;
export const SpawnResult$SpawnResult$spawn_timer = (value) => value.spawn_timer;
export const SpawnResult$SpawnResult$1 = (value) => value.spawn_timer;

function init_playing_state() {
  return new PlayingState(
    toList([]),
    new Physics(new Gravity(new Vec2(0.0, 0.08))),
    new Hud(
      new Score(
        new Vec2(1050.0, 70.0),
        new $target.Colorable($paint.colour_hex("#ffffff")),
        0,
      ),
      new CrossHair(
        new Vec2(0.0, 0.0),
        new $target.Colorable($paint.colour_hex("#ffffff")),
        5.0,
      ),
      new Health(new Vec2(30.0, 30.0), 120.0, 3, 3),
    ),
    new Vec2(0.0, 0.0),
    0.0,
  );
}

function get_target_click_effects(kind) {
  if (kind instanceof Lucy) {
    return new TargetClickEffect(1, 0);
  } else if (kind instanceof Diamond) {
    let points_value = kind.points_value;
    return new TargetClickEffect(points_value, 0);
  } else {
    return new TargetClickEffect(0, -1);
  }
}

function filter_out_of_bounds_targets(targets) {
  return $list.fold(
    targets,
    new TargetFilterResult(toList([]), 0),
    (acc, target) => {
      let $ = target.position.y > 900.0;
      if ($) {
        let $1 = target.kind;
        if ($1 instanceof Rocket) {
          return acc;
        } else {
          return new TargetFilterResult(
            acc.remaining_targets,
            acc.health_loss + 1,
          );
        }
      } else {
        return new TargetFilterResult(
          listPrepend(target, acc.remaining_targets),
          acc.health_loss,
        );
      }
    },
  );
}

function should_spawn_new_target(spawn_timer, active_targets, score) {
  let spawn_interval = 60.0;
  let max_targets = 1 + (globalThis.Math.trunc(score / 5));
  return (spawn_timer >= spawn_interval) && ($list.length(active_targets) < max_targets);
}

function handle_target_click(playing_state, clicked_target) {
  let new_targets = $list.filter(
    playing_state.targets,
    (target) => { return !isEqual(target, clicked_target); },
  );
  let effect = get_target_click_effects(clicked_target.kind);
  return new PlayingState(
    new_targets,
    playing_state.physics,
    (() => {
      let _record = playing_state.hud;
      return new Hud(
        (() => {
          let _record$1 = playing_state.hud.score;
          return new Score(
            _record$1.position,
            _record$1.colorable,
            playing_state.hud.score.score + effect.score_change,
          );
        })(),
        _record.cross_hair,
        (() => {
          let _record$1 = playing_state.hud.health;
          return new Health(
            _record$1.position,
            _record$1.size,
            playing_state.hud.health.amount + effect.health_change,
            _record$1.max_health,
          );
        })(),
      );
    })(),
    playing_state.mouse,
    playing_state.spawn_timer,
  );
}

function update_playing_state(playing_state) {
  let $ = playing_state.physics;
  let gravity;
  gravity = $.gravity;
  let updated_targets = $list.map(
    playing_state.targets,
    (target) => { return $target.update_target(target, gravity.acceleration); },
  );
  let filter_result = filter_out_of_bounds_targets(updated_targets);
  let new_spawn_timer = playing_state.spawn_timer + 1.0;
  let _block;
  let $1 = should_spawn_new_target(
    new_spawn_timer,
    filter_result.remaining_targets,
    playing_state.hud.score.score,
  );
  if ($1) {
    _block = new SpawnResult(
      listPrepend($target.create_target(), filter_result.remaining_targets),
      0.0,
    );
  } else {
    _block = new SpawnResult(filter_result.remaining_targets, new_spawn_timer);
  }
  let spawn_result = _block;
  let new_mouse = get_mouse_position();
  return new PlayingState(
    spawn_result.targets,
    playing_state.physics,
    (() => {
      let _record = playing_state.hud;
      return new Hud(
        _record.score,
        (() => {
          let _record$1 = playing_state.hud.cross_hair;
          return new CrossHair(new_mouse, _record$1.colorable, _record$1.size);
        })(),
        (() => {
          let _record$1 = playing_state.hud.health;
          return new Health(
            _record$1.position,
            _record$1.size,
            playing_state.hud.health.amount - filter_result.health_loss,
            _record$1.max_health,
          );
        })(),
      );
    })(),
    new_mouse,
    spawn_result.spawn_timer,
  );
}

function update_state(state) {
  if (state instanceof MainMenu) {
    return state;
  } else if (state instanceof Playing) {
    let playing_state = state.state;
    let prev_mouse = state.previous_mouse_pressed;
    return new Playing(update_playing_state(playing_state), prev_mouse);
  } else {
    return state;
  }
}

function find_clicked_target(mouse, targets) {
  return $list.find(
    targets,
    (target) => { return $target.is_point_in_target(mouse, target); },
  );
}

function handle_playing_click(playing_state) {
  let $ = find_clicked_target(playing_state.mouse, playing_state.targets);
  if ($ instanceof Ok) {
    let clicked_target = $[0];
    return handle_target_click(playing_state, clicked_target);
  } else {
    return playing_state;
  }
}

function handle_main_menu_click(mouse_pressed) {
  if (mouse_pressed) {
    return new Playing(init_playing_state(), false);
  } else {
    return new MainMenu();
  }
}

function handle_playing_state(playing_state, mouse_pressed) {
  let _block;
  if (mouse_pressed) {
    _block = handle_playing_click(playing_state);
  } else {
    _block = playing_state;
  }
  let new_state = _block;
  let $ = new_state.hud.health.amount <= 0;
  if ($) {
    return new GameOver(new_state.hud.score.score, mouse_pressed);
  } else {
    return new Playing(new_state, mouse_pressed);
  }
}

function handle_game_over_click(
  final_score,
  mouse_pressed,
  previous_mouse_pressed
) {
  let just_clicked = mouse_pressed && !previous_mouse_pressed;
  if (just_clicked) {
    return new Playing(init_playing_state(), false);
  } else {
    return new GameOver(final_score, mouse_pressed);
  }
}

function handle_events(state) {
  let current_mouse_pressed = is_mouse_pressed();
  if (state instanceof MainMenu) {
    return handle_main_menu_click(current_mouse_pressed);
  } else if (state instanceof Playing) {
    let playing_state = state.state;
    return handle_playing_state(playing_state, current_mouse_pressed);
  } else {
    let final_score = state.final_score;
    let previous_mouse_pressed = state.previous_mouse_pressed;
    return handle_game_over_click(
      final_score,
      current_mouse_pressed,
      previous_mouse_pressed,
    );
  }
}

function game_loop(state) {
  $rendering.render(state);
  return request_animation_frame(
    () => {
      let _pipe = handle_events(state);
      let _pipe$1 = update_state(_pipe);
      return game_loop(_pipe$1);
    },
  );
}

export function main() {
  return game_loop(new MainMenu());
}
