---@param str string
---@param start string
---@return boolean
local function starts_with(str, start)
  return str:sub(1, #start) == start
end

local state = require("lua.control.state")
local flight = require("lua.control.flight")
local autopilot = require("lua.control.autopilot")

script.on_init(function()
  state.on_init()
end)

script.on_event(
  defines.events.script_raised_built,
  ---@param e EventData.script_raised_built
  function(e) state.on_entity_created(e.entity) end
)
script.on_event(
  defines.events.on_built_entity,
  ---@param e EventData.on_built_entity
  function(e) state.on_entity_created(e.entity) end
)

script.on_event(defines.events.on_tick,
  ---@param e EventData.on_tick
  function(e)
    state.cleanup()

    for plane_id, plane_data in pairs(storage.state.plane_data) do
      assert(plane_data.entity.valid)

      local plane = plane_data.entity

      autopilot.tick_plane(plane_data)

      flight.tick_plane(plane, e.tick)

      -- These are called last since they destroy and create entities
      if plane.name == "c5-galaxy-grounded" then
        flight.tick_plane_grounded_takeoff(plane)
      elseif plane.name == "c5-galaxy-flying" then
        flight.tick_plane_flying_landing_shadow(plane)
      else
        assert(false, "Invalid plane entity name: " .. plane.name)
      end
    end

    for _, player in pairs(game.connected_players) do
      autopilot.tick_player(player)

      -- Set correct military target flag
      if player.character then
        if player
            and player.driving
            and player.vehicle
            and starts_with(player.vehicle.name, "c5-galaxy-")
        then
          if player.vehicle.name == "c5-galaxy-grounded" then
            player.character.is_military_target = true
          elseif player.vehicle.name == "c5-galaxy-flying" then
            player.character.is_military_target = false
          end
        else
          player.character.is_military_target = true
        end
      end
    end
  end
)

local function update_gui(player)
  local frame = player.gui.screen.c5g_wait_cond_frame
  local pid = storage.state.player_gui[player.index].plane
  local pdata = storage.state.plane_data[pid]

  assert(pdata ~= nil, "plane data is nil")

  frame.content.controls.c5g_automated.state = pdata.automated
  frame.content.controls.c5g_paused.state = pdata.paused

  local wconds = frame.content.c5g_scroll.wconds
  if wconds and wconds.valid then
    for _, child in pairs(wconds.children) do
      child.destroy()
    end
  end

  if #pdata.wait_conditions > 0 then
    for k, wc in pairs(pdata.wait_conditions) do
      local pos = wc.markers[#wc.markers].position

      wconds.add {
        type = "empty-widget",
        ignored_by_interaction = true,
      }.style.height = 5
      wconds.add {
        type = "line"
      }
      wconds.add {
        type = "empty-widget",
        ignored_by_interaction = true,
      }.style.height = 5

      local nframe = wconds.add {
        type = "frame",
        direction = "horizontal",
      }
      nframe.add {
        type = "label",
        caption = {
          "c5gui.route" .. (k == pdata.current_stop and "_current" or ""),
          k,
          "(" .. tostring(math.floor(pos.x)) .. "," .. tostring(math.floor(pos.y)) .. ")"
        }
      }
      nframe.add {
        type = "empty-widget",
        ignored_by_interaction = true,
      }.style.horizontally_stretchable = true

      if k == pdata.current_stop then
        nframe.add {
          type = "button",
          name = "c5g_skip",
          caption = { "c5gui.skip" },
          enabled = pdata.automated and not pdata.paused,
        }
      end

      wconds.add {
        type = "empty-widget",
        ignored_by_interaction = true,
      }.style.height = 5

      for i, group in pairs(wc.conditions) do
        if i > 1 then
          wconds.add {
            type = "empty-widget",
            ignored_by_interaction = true,
          }.style.height = 5
          wconds.add {
            type = "label",
            caption = { "c5gui.or" }
          }
          wconds.add {
            type = "empty-widget",
            ignored_by_interaction = true,
          }.style.height = 5
        end

        if #group > 0 then
          local andframe = wconds.add {
            type = "frame",
            direction = "vertical",
            style = "c5g_content",
          }
          andframe.style.vertically_stretchable = false
          for j, cond in pairs(group) do
            if j > 1 then
              andframe.add {
                type = "empty-widget",
                ignored_by_interaction = true,
              }.style.height = 5
              andframe.add {
                type = "label",
                caption = { "c5gui.and" }
              }
              andframe.add {
                type = "empty-widget",
                ignored_by_interaction = true,
              }.style.height = 5
            end

            local cframe = andframe.add {
              type = "frame",
              name = "grp_" .. tostring(k) .. "_" .. tostring(i) .. "_" .. tostring(j),
              direction = "horizontal",
              style = "c5g_content",
            }
            cframe.style.vertically_stretchable = false
            cframe.add {
              type = "empty-widget",
              ignored_by_interaction = true,
            }.style.width = 20

            local opidx = 0
            for idx, v in pairs({ "=", "!=", ">", ">=", "<", "<=" }) do
              if v == cond.op then
                opidx = idx
                break
              end
            end

            if cond.type == 'time_passed' then
              cframe.add {
                type = "label",
                caption = { "c5gui.cond_time_passed" },
              }
              cframe.add {
                type = "empty-widget",
                ignored_by_interaction = true,
              }.style.width = 50
              cframe.add {
                type = "textfield",
                numeric = true,
                name = "c5g_value",
                text = tostring(cond.value),
              }
            elseif cond.type == 'fuel_empty' then
              cframe.add {
                type = "label",
                caption = { "c5gui.cond_fuel_empty" }
              }
            elseif cond.type == 'fuel_full' then
              cframe.add {
                type = "label",
                caption = { "c5gui.cond_fuel_full" }
              }
            elseif cond.type == 'cargo_empty' then
              cframe.add {
                type = "label",
                caption = { "c5gui.cond_cargo_empty" }
              }
            elseif cond.type == 'cargo_full' then
              cframe.add {
                type = "label",
                caption = { "c5gui.cond_cargo_full" }
              }
            elseif cond.type == 'fuel' then
              cframe.add {
                type = "label",
                caption = { "c5gui.cond_fuel" },
              }
              local item = cframe.add {
                type = "choose-elem-button",
                name = "c5g_item",
                elem_type = "item-with-quality",
              }
              if cond.item and string.len(cond.item.name) > 0 then
                item.elem_value = cond.item
              end
              cframe.add {
                type = "empty-widget",
                ignored_by_interaction = true,
              }.style.width = 25
              cframe.add {
                type = "drop-down",
                name = "c5g_op",
                items = {
                  { "c5gui.op_eq" },
                  { "c5gui.op_ne" },
                  { "c5gui.op_gt" },
                  { "c5gui.op_ge" },
                  { "c5gui.op_lt" },
                  { "c5gui.op_le" },
                },
                selected_index = opidx,
              }
              cframe.add {
                type = "empty-widget",
                ignored_by_interaction = true,
              }.style.width = 25
              cframe.add {
                type = "textfield",
                numeric = true,
                name = "c5g_value",
                text = tostring(cond.value)
              }
            elseif cond.type == 'cargo' then
              cframe.add {
                type = "label",
                caption = { "c5gui.cond_cargo" }
              }
              local item = cframe.add {
                type = "choose-elem-button",
                name = "c5g_item",
                elem_type = "item-with-quality",
              }
              if cond.item and string.len(cond.item.name) > 0 then
                item.elem_value = cond.item
              end
              cframe.add {
                type = "empty-widget",
                ignored_by_interaction = true,
              }.style.width = 25
              cframe.add {
                type = "drop-down",
                name = "c5g_op",
                items = {
                  { "c5gui.op_eq" },
                  { "c5gui.op_ne" },
                  { "c5gui.op_gt" },
                  { "c5gui.op_ge" },
                  { "c5gui.op_lt" },
                  { "c5gui.op_le" },
                },
                selected_index = opidx,
              }
              cframe.add {
                type = "empty-widget",
                ignored_by_interaction = true,
              }.style.width = 25
              cframe.add {
                type = "textfield",
                numeric = true,
                name = "c5g_value",
                text = tostring(cond.value),
              }
            end

            cframe.add {
              type = "empty-widget",
              ignored_by_interaction = true,
            }.style.horizontally_stretchable = true
            cframe.add {
              type = "button",
              name = "c5g_cond_remove",
              caption = { "c5gui.x" }
            }.style.width = 40
          end
        end

        local cframe = wconds.add {
          type = "frame",
          name = "grp_" .. tostring(k) .. "_" .. tostring(i),
          direction = "horizontal",
        }
        cframe.style.vertically_stretchable = false
        cframe.add {
          type = "empty-widget",
          ignored_by_interaction = true,
        }.style.width = 20
        cframe.add {
          type = "button",
          name = "c5g_cond_and",
          caption = { "c5gui.cond_and_" .. (#group > 0 and "n" or "0") }
        }
        cframe.add {
          type = "drop-down",
          name = "cond_type",
          selected_index = 1,
          items = {
            { "c5gui.cond_time_passed" },
            { "c5gui.cond_fuel_empty" },
            { "c5gui.cond_fuel_full" },
            { "c5gui.cond_cargo_empty" },
            { "c5gui.cond_cargo_full" },
            { "c5gui.cond_fuel" },
            { "c5gui.cond_cargo" },
          },
        }
        cframe.add {
          type = "empty-widget",
          ignored_by_interaction = true,
        }.style.horizontally_stretchable = true
        cframe.add {
          type = "button",
          name = "c5g_grp_remove",
          caption = { "c5gui.x" }
        }.style.width = 40
      end

      wconds.add {
        type = "empty-widget",
        ignored_by_interaction = true,
      }.style.height = 5
      wconds.add {
        type = "button",
        name = "c5g_cond_or_" .. tostring(k),
        caption = { "c5gui.cond_or_" .. (#wc.conditions > 0 and "n" or "0") }
      }
    end
  else
    wconds.add {
      type = "label",
      caption = { "c5gui.no_stops" }
    }
  end
end

script.on_event(defines.events.on_player_selected_area,
  ---@param e EventData.on_player_selected_area
  function(e)
    if e.item == "c5-galaxy-tool-config" then
      local player = game.players[e.player_index]

      local plane_count = 0
      local marker_count = 0
      local plane
      for _, curr_entity in ipairs(e.entities) do
        if curr_entity.name == "c5-galaxy-grounded" then
          plane_count = plane_count + 1
          plane = curr_entity
          if plane_count > 1 then
            player.print({ "c5-galaxy.error-too-many-planes-selected" })
            return
          end
        end
      end
      if not plane then
        return
      end

      local plane_id = storage.state.entity_to_plane_id[plane.unit_number]
      local plane_data = storage.state.plane_data[plane_id]

      if storage.state.player_gui == nil then
        storage.state.player_gui = {}
      end

      if storage.state.player_gui[player.index] ~= nil and storage.state.player_gui[player.index].plane ~= nil then
        player.gui.screen.c5g_wait_cond_frame.destroy()
      end

      storage.state.player_gui[player.index] = {
        plane = plane_id
      }

      local screen = player.gui.screen
      local frame = screen.add {
        type = "frame",
        name = "c5g_wait_cond_frame",
        caption = { "c5gui.caption" },
      }
      frame.style.size = { 1000, 750 }
      frame.auto_center = true

      local content = frame.add {
        type = "frame",
        name = "content",
        direction = "vertical",
        style = "c5g_content",
      }
      local controls = content.add {
        type = "flow",
        name = "controls",
        direction = "horizontal",
        style = "c5g_controls",
      }
      content.add {
        type = "empty-widget",
        ignored_by_interaction = true,
      }.style.height = 20
      local scroll = content.add {
        type = "scroll-pane",
        name = "c5g_scroll",
        horizontal_scroll_policy = "never",
      }
      content.add {
        type = "empty-widget",
        ignored_by_interaction = true,
      }.style.height = 20
      local controls2 = content.add {
        type = "flow",
        name = "controls2",
        direction = "horizontal",
        style = "c5g_controls",
      }

      controls.add {
        type = "checkbox",
        name = "c5g_automated",
        caption = { "c5gui.automated" },
        state = false,
      }
      controls.add {
        type = "checkbox",
        name = "c5g_paused",
        caption = { "c5gui.paused" },
        state = false,
      }
      controls.add {
        type = "empty-widget",
        ignored_by_interaction = true,
      }.style.horizontally_stretchable = true
      controls.add {
        type = "button",
        name = "c5g_clear",
        caption = { "c5gui.clear" },
      }

      scroll.add {
        type = "frame",
        name = "wconds",
        direction = "vertical",
        style = "c5g_content",
      }

      controls2.add {
        type = "empty-widget",
        ignored_by_interaction = true,
      }.style.horizontally_stretchable = true
      controls2.add {
        type = "button",
        name = "c5g_close",
        caption = { "c5gui.close" },
      }

      update_gui(player)

      return
    end

    autopilot.on_player_selected_area(e)
  end
)
script.on_event(defines.events.on_player_reverse_selected_area,
  ---@param e EventData.on_player_reverse_selected_area
  function(e)
    autopilot.on_player_reverse_selected_area(e)
  end
)



local function check_gui_allowed(event)
  if not starts_with(event.element.name, "c5g_") then
    return false
  end

  local pid = storage.state.player_gui[event.player_index].plane
  local pdata = storage.state.plane_data[pid]
  local player = game.get_player(event.player_index)

  if pdata.autopilot_data ~= nil then
    player.print({ "c5-galaxy.error-cannot-modify-moving-plane" })
    update_gui(player)
    return false
  end

  return true
end

script.on_event(defines.events.on_gui_click, function(event)
  local player = game.get_player(event.player_index)

  if event.element.name == "c5g_close" then
    storage.state.player_gui[player.index] = nil
    player.gui.screen.c5g_wait_cond_frame.destroy()
    return
  end

  if not check_gui_allowed(event) then
    return
  end

  local pid = storage.state.player_gui[event.player_index].plane
  local pdata = storage.state.plane_data[pid]
  local player = game.get_player(event.player_index)

  if event.element.name == "c5g_clear" then
    pdata.wait_conditions = {}
    pdata.current_stop = 1
    update_gui(player)
  elseif starts_with(event.element.name, "c5g_cond_or_") then
    local grp = tonumber(string.match(event.element.name, "c5g_cond_or_(%d+)"))
    table.insert(pdata.wait_conditions[grp].conditions, {})
    update_gui(player)
  elseif event.element.name == "c5g_grp_remove" then
    local a, b = string.match(event.element.parent.name, "grp_(%d+)_(%d+)")
    table.remove(pdata.wait_conditions[tonumber(a)].conditions, tonumber(b))
    update_gui(player)
  elseif event.element.name == "c5g_cond_and" then
    local a, b = string.match(event.element.parent.name, "grp_(%d+)_(%d+)")
    local idx = event.element.parent.cond_type.selected_index

    if idx == 0 then
      return
    end

    local idx2type = {
      "time_passed",
      "fuel_empty",
      "fuel_full",
      "cargo_empty",
      "cargo_full",
      "fuel",
      "cargo",
    }

    table.insert(pdata.wait_conditions[tonumber(a)].conditions[tonumber(b)], {
      type = idx2type[idx],
      item = { name = "", quality = "" },
      op = "=",
      value = 0,
    })
    update_gui(player)
  elseif event.element.name == "c5g_cond_remove" then
    local a, b, c = string.match(event.element.parent.name, "grp_(%d+)_(%d+)_(%d+)")
    table.remove(pdata.wait_conditions[tonumber(a)].conditions[tonumber(b)], tonumber(c))
    update_gui(player)
  elseif event.element.name == "c5g_skip" then
    pdata.skip = true
    update_gui(player)
  end
end)

script.on_event(defines.events.on_gui_checked_state_changed, function(event)
  if event.element.name == "c5g_paused" then
    local pid = storage.state.player_gui[event.player_index].plane
    local pdata = storage.state.plane_data[pid]
    local player = game.get_player(event.player_index)

    pdata.paused = event.element.state
    update_gui(player)
    return
  end

  if not check_gui_allowed(event) then
    return
  end

  local pid = storage.state.player_gui[event.player_index].plane
  local pdata = storage.state.plane_data[pid]
  local player = game.get_player(event.player_index)

  if event.element.name == "c5g_automated" then
    pdata.automated = event.element.state
    update_gui(player)
  end
end)

script.on_event(defines.events.on_gui_confirmed, function(event)
  if not check_gui_allowed(event) then
    return
  end

  local pid = storage.state.player_gui[event.player_index].plane
  local pdata = storage.state.plane_data[pid]
  local player = game.get_player(event.player_index)

  if event.element.name == "c5g_value" then
    local a, b, c = string.match(event.element.parent.name, "grp_(%d+)_(%d+)_(%d+)")
    pdata.wait_conditions[tonumber(a)].conditions[tonumber(b)][tonumber(c)].value = tonumber(event.element.text)
    update_gui(player)
  end
end)

script.on_event(defines.events.on_gui_selection_state_changed, function(event)
  if not check_gui_allowed(event) then
    return
  end

  local pid = storage.state.player_gui[event.player_index].plane
  local pdata = storage.state.plane_data[pid]
  local player = game.get_player(event.player_index)
  local ops = { "=", "!=", ">", ">=", "<", "<=" }

  if event.element.name == "c5g_op" then
    local a, b, c = string.match(event.element.parent.name, "grp_(%d+)_(%d+)_(%d+)")
    pdata.wait_conditions[tonumber(a)].conditions[tonumber(b)][tonumber(c)].op = ops[event.element.selected_index] or ""
    update_gui(player)
  end
end)

script.on_event(defines.events.on_gui_elem_changed, function(event)
  if not check_gui_allowed(event) then
    return
  end

  local pid = storage.state.player_gui[event.player_index].plane
  local pdata = storage.state.plane_data[pid]
  local player = game.get_player(event.player_index)

  if event.element.name == "c5g_item" then
    local a, b, c = string.match(event.element.parent.name, "grp_(%d+)_(%d+)_(%d+)")
    pdata.wait_conditions[tonumber(a)].conditions[tonumber(b)][tonumber(c)].item = event.element.elem_value
    update_gui(player)
  end
end)
