--- @name         Session goal
--- @description  Sitting count. WC peeks, GOAL sets
--- @type         watcher
--- @requires     document
--- @caps         doc.edit, data.read, data.write, ui.alert

-- Session goal. Listens for WORD only. Tab and Enter are left to other
-- watchers: the firmware allows one text-editing watcher per event, and
-- the Expander edits on both, so listening here would keep the two from
-- running together.
--
--   Each finished word adds one. Backspace does not subtract: this is
--   words typed this sitting, not the file total the editor already shows.
--
--   WC then space    -- peek the sitting. The letters are removed.
--   GOAL then 2000   -- set the target. A space finishes the number.
--   RESET then space -- sitting starts again, goal kept.
--
--   One alert at the goal, then quiet until RESET or a new GOAL.
--   Commands are the all-caps words above. Ordinary "goal" in a sentence
--   is left alone. The space that finished the command is removed with
--   it, so the cursor is where it was before the command was typed.

local can_save = true
local count    = 0
local goal     = 500
local hit      = false
local pending  = false
local unsaved  = 0

local SAVE_EVERY = 25

local function trim(text)
  return (text:gsub("^%s+", ""):gsub("%s+$", ""))
end

local function upper(text)
  return string.upper(text)
end

local function load_data()
  local text, why = BYOK.data.read()
  if text then
    for raw in text:gmatch("[^\r\n]+") do
      local entry = trim(raw)
      if entry ~= "" and not entry:match("^#") then
        local kind, value = entry:match("^([^|]+)|(.*)$")
        if kind and value then
          kind = upper(trim(kind))
          value = trim(value)
          if kind == "COUNT" then count = tonumber(value) or count end
          if kind == "GOAL" then goal = tonumber(value) or goal end
          if kind == "HIT" then hit = (value == "1") end
        end
      end
    end
  elseif why ~= "missing" then
    can_save = false
  end
  if goal < 0 then goal = 0 end
  if count < 0 then count = 0 end
end

local function save_data()
  if not can_save then return end
  local lines = {
    "# Session goal. COUNT is this sitting, GOAL is the target.",
    "# WC peeks, GOAL then a number sets the target, RESET starts over.",
    "COUNT|" .. count,
    "GOAL|" .. goal,
    "HIT|" .. (hit and "1" or "0"),
  }
  if not BYOK.data.write(table.concat(lines, "\n") .. "\n") then
    can_save = false
  end
  unsaved = 0
end

local function peek_body()
  if goal > 0 then return count .. " / " .. goal end
  return tostring(count)
end

local function goal_title()
  if goal > 0 then return "Goal is " .. goal end
  return "No goal"
end

-- WORD's range is the letters only. The space that finished the word
-- is the next byte. Delete letters + that byte so nothing is left
-- in front of whatever they type next.
local function eat(from, word)
  BYOK.doc.replaceRange(from, from + #word + 1, "")
end

local function set_goal(n)
  goal = n
  hit = (goal > 0 and count >= goal)
  save_data()
end

local function bump()
  count = count + 1
  unsaved = unsaved + 1
  if goal > 0 and not hit and count >= goal then
    hit = true
    save_data()
    BYOK.ui.alert("Goal met", "Session target reached")
    return
  end
  if unsaved >= SAVE_EVERY then save_data() end
end

function init()
  load_data()
  BYOK.sys.registerEvent("WORD")
end

local function valid_goal(word)
  local n = tonumber(word)
  if n and n == math.floor(n) and n >= 1 and n <= 999999 then return n end
  return nil
end

function listen(event, text, from, to)
  if event ~= "WORD" then return end

  local word = trim(text)
  if word == "" then return end

  if pending then
    pending = false
    local n = valid_goal(word)
    if n then
      eat(from, word)
      set_goal(n)
      BYOK.ui.alert("Goal is " .. n, "Session target")
    else
      BYOK.ui.alert("Goal not set", "Type a number, like 2000")
    end
    return
  end

  if word == "WC" then
    eat(from, word)
    save_data()
    BYOK.ui.alert("Session goal", peek_body())
    return
  end

  if word == "GOAL" then
    pending = true
    eat(from, word)
    return
  end

  if word == "RESET" then
    eat(from, word)
    count = 0
    hit = false
    save_data()
    BYOK.ui.alert("Session reset", goal_title())
    return
  end

  bump()
end
