--[[
  Copyright (c) 2024 - 2026 by Plexim GmbH
  All rights reserved.

  A free license is granted to anyone to use this software for any legal
  non safety-critical purpose, including commercial applications, provided
  that:
  1) IT IS NOT USED TO DIRECTLY OR INDIRECTLY COMPETE WITH PLEXIM, and
  2) THIS COPYRIGHT NOTICE IS PRESERVED in its entirety.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  SOFTWARE.
--]]

local iL = {}
local R = 10                -- Radius for block and badge corners
local defaultCharWidth = 4  -- used for characters not defined in the alphabet below
local charSize = 7          -- font size

local alphabet = {
  A = 4,
  B = 4,
  C = 4,
  D = 4,
  E = 4,
  F = 4,
  G = 4,
  H = 4,
  I = 3.5,
  J = 4,
  K = 4,
  L = 4,
  M = 6,
  N = 4,
  O = 4,
  P = 4,
  Q = 4,
  R = 4,
  S = 4,
  T = 4,
  U = 4,
  V = 4,
  W = 6,
  X = 4,
  Y = 4,
  Z = 4,
  a = 3.5,
  b = 3.5,
  c = 3.5,
  d = 4,
  e = 3.5,
  f = 3.5,
  g = 4,
  h = 4,
  i = 2.5,
  j = 3.5,
  k = 4,
  l = 3.5,
  m = 5.5,
  n = 3.5,
  o = 3.5,
  p = 4,
  q = 4,
  r = 2.5,
  s = 3.5,
  t = 3.5,
  u = 3.5,
  v = 3.5,
  w = 5,
  x = 3.5,
  y = 4,
  z = 3.5
}

local color = {
  white = {255, 255, 255},
  black = {0, 0, 0},
  exp_grey_bg = {117, 117, 117},
  microchip_red = {212, 53, 54},
  ti_red = {187, 39, 26},  -- ti.com banner
  xmc_blue = {49, 99, 216},
}

local EXP_TSP = {
  -- add entries here to map experimental components to the corresponding settings
  dsPICX = 'dsPIC',
  SAMX = 'SAM',
  STX = 'ST',
  TIX = 'TI',
  XMCX = 'XMC',
}

local TSP = {
  -- define colors as {light = {R, G, B}} if it should remain the same for light and darkmode. Otherwise define {light = {R, G, B}, dark = {R, G, B}}
  default = {
    patchSize = 3,          -- horizontal padding
    color_bg = {light = color.white, dark = color.black},
    color_txt = 'normalFg'  -- uses the PLECS defaults
  },
  dsPIC = {
    patchSize = 3,
    color_bg = {light = color.microchip_red},
    color_txt = {light = color.white},
  },
  SAM = {
    patchSize = 4,
    color_bg = {light = color.microchip_red},
    color_txt = {light = color.white},
  },
  ST = {
    patchSize = 3,
    color_bg = {light = {4, 34, 73}, dark = {55, 110, 155}},
    color_txt = {light = color.white},
  },
  TI = {
    patchSize = 3,
    color_bg = {light = color.ti_red},
    color_txt = {light = color.white},
  },
  XMC = {
    patchSize = 4,
    color_bg = {light = color.xmc_blue},
    color_txt = {light = color.white}
  },
}

local function _fetchTspSettings(tsp_name)
  local isExperimental = false
  local tsp_display_name

  -- check if block is experimental
  if EXP_TSP[tsp_name] then
    isExperimental = true
    tsp_display_name = EXP_TSP[tsp_name]  -- remap display name (remove the 'X')
  else
    tsp_display_name = tsp_name
  end

  local settings = TSP[tsp_display_name]

  if settings == nil then  -- target settings not found
    settings = TSP.default
  end

  if isExperimental then
    -- handle special case for experimental blocks (grey background)
    settings.color_bg = color.exp_grey_bg
    settings.color_txt = color.white
  end

  settings.display_name = tsp_display_name

  return settings
end

-- set the text for a block
-- optionally provide x and y shifts or use default shift for multiple lines.
function iL.setBlockName(block_name, x, y)
  local x_shift = 0  -- center text by default
  -- vertically shift text that has multiple lines to give space for the tag
  local y_shift = string.find(block_name, '\n') and 3 or 0

  if type(x) == 'number' then
    x_shift = x
  end

  if type(y) == 'number' then
    y_shift = y + y_shift -- add the default here for more logical UI
  end
  local direction = Block:get('Direction')
  if direction == 0.25 or direction == 0.75 then
    Icon:text(x_shift, y_shift, block_name)
  else
    Icon:text(y_shift, x_shift, block_name)
  end
end

-- helper function for iL.drawDecoration()
local function _drawEmptyPatch(x1, y1, x2, y2, pos)
  local ptx = {}
  local pty = {}
  local radius = R - 0.1
  if pos then
    for i = 0, 90 do
      local angle = i * math.pi / 180
      table.insert(ptx, x1 + radius * math.cos(angle))
      table.insert(pty, y1 + radius * math.sin(angle))
    end
    for i = 180, 270 do
      local angle = i * math.pi / 180
      table.insert(ptx, x2 + radius * math.cos(angle))
      table.insert(pty, y2 + radius * math.sin(angle))
    end
  else
    for i = 90, 180 do
      local angle = i * math.pi / 180
      table.insert(ptx, x1 + radius * math.cos(angle))
      table.insert(pty, y1 + radius * math.sin(angle))
    end
    for i = 270, 360 do
      local angle = i * math.pi / 180
      table.insert(ptx, x2 + radius * math.cos(angle))
      table.insert(pty, y2 + radius * math.sin(angle))
    end
  end

  Icon:patch(ptx, pty)
end

-- helper function for iL.drawDecoration(), draws patch borders
local function _drawPatchBorders(x1, y1, x2, y2, patchSize, pos)
  local dir = nil
  if pos then
    dir = -90
  else
    dir = 90
  end

  Icon:arc(x1, y1, R, R, 180, dir)
  Icon:arc(x2, y2, R, R, 0, dir)
  Icon:line({x1 - R + 0.5, x1 - R + patchSize - 0.5}, {y1, y1})
  Icon:line({x2 + R - patchSize + 0.5, x2 + R - 0.5}, {y2, y2})
end

--[[
  Draw a patch to highlight TSP -blocks from regular PLECS blocks and/or other TSP -blocks.
  The TSP's supported by PLECS are pre -defined (color and text) and cannot be modified from the mask.
--]]
function iL.drawPatch(raw_tsp_name)
  local tar = _fetchTspSettings(raw_tsp_name)

  local tsp_name = tar.display_name
  local color_bg = tar.color_bg
  local color_txt = tar.color_txt

  Icon:color(color_bg)

  local flipped = Block:get('Flipped')
  local direction = Block:get('Direction')
  local frame = Block:get('Frame')
  local x_start = frame[1]
  local y_start = frame[2]
  local x_total = frame[3]
  local y_total = frame[4]

  local l = string.len(tsp_name)
  for i = 1, l do
    local thisCharWidth = alphabet[string.sub(tsp_name, i, i)]
    if thisCharWidth == nil then
      tar.patchSize = tar.patchSize + defaultCharWidth
    else
      tar.patchSize = tar.patchSize + thisCharWidth
    end
  end
  local delta = (tar.patchSize + R) / 2

  local x, y, g, position
  local xT = nil
  local yT = nil

  if direction == 0.0 then
    x = -x_start + y_start
    y = x_start - y_start
    g = -1
    if flipped then
      xT = x_start + R / 2
      yT = y_start + delta
      position = 'upLeft'
    else
      xT = x_start + R / 2
      yT = -y_start - delta
      position = 'upRight'
    end
  end

  if direction == 0.25 then
    x = 0
    y = 0
    g = 1
    if flipped then
      xT = -x_start - delta
      yT = y_start + R / 2
      position = 'upRight'
    else
      xT = -x_start - delta
      yT = -y_start - R / 2
      position = 'lowRight'
    end
  end

  if direction == 0.5 then
    x = -x_start + y_start
    y = x_start - y_start
    g = -1
    if flipped then
      xT = -x_start - R / 2
      yT = -y_start - delta
      position = 'lowRight'
    else
      xT = -x_start - R / 2
      yT = y_start + delta
      position = 'lowLeft'
    end
  end

  if direction == 0.75 then
    x = 0
    y = 0
    g = 1
    if flipped then
      xT = x_start + delta
      yT = -y_start - R / 2
      position = 'lowLeft'
    else
      xT = x_start + delta
      yT = y_start + R / 2
      position = 'upLeft'
    end
  end

  if position == 'upLeft' then
    _drawEmptyPatch((x_start + tar.patchSize + x), (y_start + y), (x_start + R + x), (y_start + R + y), true)
    Icon:color('normalFg')
    _drawPatchBorders((x_start + R + x), (y_start + R + y),
                      (x_start + tar.patchSize + x), (y_start + y), tar.patchSize, true)
    Icon:text(xT, yT, [[<b><i>]]..tsp_name, 'FontSize', charSize, 'TextFormat', 'RichText', 'Color', color_txt)
  elseif position == 'upRight' then
    _drawEmptyPatch((-x_start - tar.patchSize - x), (y_start + y), (-x_start - R - x), (y_start + R + y), false)
    Icon:color('normalFg')
    _drawPatchBorders((-x_start - tar.patchSize - x), (y_start + y),
                      (-x_start - R - x), (y_start + R + y), tar.patchSize, false)
    Icon:text(g * -xT, g * yT, [[<b><i>]]..tsp_name, 'FontSize', charSize, 'TextFormat', 'RichText', 'Color', color_txt)
  elseif position == 'lowRight' then
    _drawEmptyPatch((-x_start - R - x), (-y_start - R - y), (-x_start - tar.patchSize - x), (-y_start - y), true)
    Icon:color('normalFg')
    _drawPatchBorders((-x_start - tar.patchSize - x), (-y_start - y),
                      (-x_start - R - x), (-y_start - R - y), tar.patchSize, true)
    Icon:text(-xT, -yT, [[<b><i>]]..tsp_name, 'FontSize', charSize, 'TextFormat', 'RichText', 'Color', color_txt)
  elseif position == 'lowLeft' then
    _drawEmptyPatch((x_start + R + x), (-y_start - R - y), (x_start + tar.patchSize + x), (-y_start - y), false)
    Icon:color('normalFg')
    _drawPatchBorders((x_start + R + x), (-y_start - R - y), 
                      (x_start + tar.patchSize + x), (-y_start - y), tar.patchSize, false)
    Icon:text(g * xT, g * -yT, [[<b><i>]]..tsp_name, 'FontSize', charSize, 'TextFormat', 'RichText', 'Color', color_txt)
  end
end

-- helper function for iL.drawTrigger()
local function _drawTriggerLines(x, y, trigger_type)
  if trigger_type == 'rising' then
    Icon:line({x, x}, {y - 3, y + 3})
    Icon:line({x, x + 2.5}, {y - 3, y - 3})
    Icon:line({x, x - 2.5}, {y + 3, y + 3})
    Icon:line({x - 2, x}, {y + 1, y - 1})
    Icon:line({x + 2, x}, {y + 1, y - 1})
  elseif trigger_type == 'falling' then
    Icon:line({x, x}, {y - 3, y + 3})
    Icon:line({x, x + 2.5}, {y + 3, y + 3})
    Icon:line({x, x - 2.5}, {y - 3, y - 3})
    Icon:line({x - 2, x}, {y - 1, y + 1})
    Icon:line({x + 2, x}, {y - 1, y + 1})
  elseif trigger_type == 'either' then
    x = x - 2.5
    Icon:line({x, x}, {y - 3, y + 3})
    Icon:line({x, x + 2.5}, {y - 3, y - 3})
    Icon:line({x, x - 2.5}, {y + 3, y + 3})
    Icon:line({x - 2, x}, {y + 1, y - 1})
    Icon:line({x + 2, x}, {y + 1, y - 1})
    x = x + 5
    Icon:line({x, x}, {y - 3, y + 3})
    Icon:line({x, x + 2.5}, {y + 3, y + 3})
    Icon:line({x, x - 2.5}, {y - 3, y - 3})
    Icon:line({x - 2, x}, {y - 1, y + 1})
    Icon:line({x + 2, x}, {y - 1, y + 1})
  elseif trigger_type == 'levelHigh' then
    x = x - 1.5
    Icon:line({x, x}, {y - 3, y + 3})
    Icon:line({x, x + 2.5}, {y - 3, y - 3})
    Icon:line({x, x - 2}, {y + 3, y + 3})
    x = x + 3
    Icon:line({x, x}, {y - 3, y + 3})
    Icon:line({x, x + 2}, {y + 3, y + 3})
    Icon:line({x, x - 2.5}, {y - 3, y - 3})
  elseif trigger_type == 'levelLow' then
    x = x - 1.5
    Icon:line({x, x}, {y - 3, y + 3})
    Icon:line({x, x + 2.5}, {y + 3, y + 3})
    Icon:line({x, x - 2}, {y - 3, y - 3})
    x = x + 3
    Icon:line({x, x}, {y - 3, y + 3})
    Icon:line({x, x + 2}, {y - 3, y - 3})
    Icon:line({x, x - 2.5}, {y + 3, y + 3})

  end
end

-- draw trigger symbol for blocks that can be triggered by other blocks (i.e. ADC) 
-- and specify the location of the symbol, optionally add triggerType
function iL.drawTrigger(x, y, triggerType)
  triggerType = tonumber(triggerType) or 1 -- default to rising
  
  -- TriggerTypeName list must match block dialog 
  -- this is weirdly brittle, all dialogs must define these as 1, 2, 3
  -- Note that XMC and STM implement the 'none' option as 1, so these should be
  -- zero indexed (with a minus 1) before being passed in.
  local TriggerTypeName = {[0] = 'none', 'rising', 'falling', 'either', 'levelHigh', 'levelLow'}
  local t = TriggerTypeName[triggerType]

  if t == 'none' then
    return -- skip drawing the trigger
  end

  local flipped = Block:get('Flipped')
  local direction = Block:get('Direction')

  Icon:color('normalFg')

  if direction == 0.0 then
    if flipped then
      _drawTriggerLines(-y, x, t)
    else
      _drawTriggerLines(y, x, t)
    end
  end
  if direction == 0.25 then
    if flipped then
      _drawTriggerLines(-x, -y, t)
    else
      _drawTriggerLines(-x, y, t)
    end
  end
  if direction == 0.5 then
    if flipped then
      _drawTriggerLines(y, -x, t)
    else
      _drawTriggerLines(-y, -x, t)
    end
  end
  if direction == 0.75 then
    if flipped then
      _drawTriggerLines(x, y, t)
    else
      _drawTriggerLines(x, -y, t)
    end
  end
end

local function _drawSyncIconLines(x, y)
  -- helper function for iL.drawSyncIcon()
  Icon:line({x - 3.5, x + 3.5}, {y + 2.5, y + 2.5})
  Icon:line({x, x}, {y + 2.5, y - 2.5})
  Icon:line({x - 2.5, x - 2.5}, {y + 2.5, y - 2.5})
  Icon:line({x + 2.5, x + 2.5}, {y + 2.5, y - 2.5})
  Icon:circle(x, y - 2.5, 0.5)
  Icon:circle(x - 2.5, y - 2.5, 0.5)
  Icon:circle(x + 2.5, y - 2.5, 0.5)
end

-- draw synchronize symbol for blocks that can be synchronized with other blocks and specify the location of the symbol
function iL.drawSyncIcon(x, y)
  local flipped = Block:get('Flipped')
  local direction = Block:get('Direction')
  Icon:color('event')
  if direction == 0.0 then
    if flipped then
      _drawSyncIconLines(-y, x)
    else
      _drawSyncIconLines(y, x)
    end
  end
  if direction == 0.25 then
    if flipped then
      _drawSyncIconLines(-x, -y)
    else
      _drawSyncIconLines(-x, y)
    end
  end
  if direction == 0.5 then
    if flipped then
      _drawSyncIconLines(y, -x)
    else
      _drawSyncIconLines(-y, -x)
    end
  end
  if direction == 0.75 then
    if flipped then
      _drawSyncIconLines(x, y)
    else
      _drawSyncIconLines(x, -y)
    end
  end
end

-- Helper function that returns comparator drawing functions with transformation applied
-- Coordinate system: positive x is right, positive y is down (PLECS convention)
-- Direction values: 0.0 = right, 0.25 = down, 0.5 = left, 0.75 = up
-- Flipped: horizontal flip for directions 0.0 and 0.5; flip + 180° rotation for directions 0.25 and 0.75
local function _drawCompHelperFunctions(x0, y0, direction, flipped)
  -- Transform local coordinates to block coordinates by applying rotation, flip, and translation
  local function _rotate(x_local, y_local)
    local x_block, y_block

    -- Apply rotation based on block direction, then flip, then translate
    if direction == 0.0 then
      -- Direction: right (0°)
      -- Flipped: horizontal flip (mirror across vertical axis)
      x_block = x_local + x0
      y_block = y_local + y0
      if flipped then
        x_block = -x_block
      end
    elseif direction == 0.25 then
      -- Direction: down (90° CW rotation)
      -- Flipped: horizontal flip + 180° rotation (equivalent to up direction with x-flip)
      x_block = -y_local + x0
      y_block = x_local + y0
      if flipped then
        y_block = -y_block
      end
    elseif direction == 0.5 then
      -- Direction: left (180° rotation)
      -- Flipped: horizontal flip (mirror across vertical axis)
      x_block = -x_local + x0
      y_block = -y_local + y0
      if flipped then
        x_block = -x_block
      end
    else --direction == 0.75
      -- Direction: up (270° CW rotation)
      -- Flipped: horizontal flip + 180° rotation (equivalent to down direction with x-flip)
      x_block = y_local + x0
      y_block = -x_local + y0
      if flipped then
        y_block = -y_block
      end
    end

    return x_block, y_block
  end

  -- Draw a polyline with transformation applied
  local function _lineR(xs, ys)
    local rx, ry = {}, {}
    for i = 1, #xs do
      rx[i], ry[i] = _rotate(xs[i], ys[i])
    end
    Icon:line(rx, ry)
  end

  -- Draw a circle with transformation applied
  local function _circleR(x, y, r)
    local rx, ry = _rotate(x, y)
    Icon:circle(rx, ry, r)
  end

  -- Draw a comparator input pin (vertical line + optional circle at end)
  local function _drawCompInPin(x, y, INPUT_PIN_LENGTH, INPUT_PIN_CIRCLE_OFFSET, INPUT_PIN_CIRCLE_RADIUS, drawCircle)
    drawCircle = drawCircle == nil or drawCircle  -- default to true if not specified
    _lineR({x, x}, {y, y - INPUT_PIN_LENGTH})
    if drawCircle then
      _circleR(x, y - INPUT_PIN_CIRCLE_OFFSET, INPUT_PIN_CIRCLE_RADIUS)
    end
  end

  return {
    _rotate = _rotate,
    _lineR = _lineR,
    _circleR = _circleR,
    _drawCompInPin = _drawCompInPin
  }
end

-- Internal helper function to draw a comparator at a specific position
-- comparatorCenterCoords: table {x, y} or {[1], [2]} for center position
-- opt_config: optional table with {isInverted, negInputFromModel, showHyst, showOutExt, swapInputs, dims, showPosInputCircle, disconnectedOut}
local function _drawComparatorAt(comparatorCenterCoords, opt_config,direction,flipped)
  -- =========================
  -- Constants for comparator dimensions
  -- =========================
  -- Use provided optional configuration or defaults
  opt_config = opt_config or {}
  local isInverted = opt_config.isInverted or false
  local negInputFromModel = opt_config.negInputFromModel or false
  local showHyst = opt_config.showHyst or false
  local showOutExt = opt_config.showOutExt or false
  local opt_dims = opt_config.dims or {}
  local opt_swapInputs = opt_config.swapInputs or false
  local showPosInputCircle = opt_config.showPosInputCircle or false
  local opt_disconnectedOut = opt_config.disconnectedOut or false

  -- Extract position coordinates
  local x0 = comparatorCenterCoords.x or comparatorCenterCoords[1] or 0
  local y0 = comparatorCenterCoords.y or comparatorCenterCoords[2] or 0

  local COMP_HALF_WIDTH = opt_dims.COMP_HALF_WIDTH or 13        -- Half-width of triangular body
  local COMP_HALF_HEIGHT = opt_dims.COMP_HALF_HEIGHT or 13       -- Half-height of triangular body
  local COMP_OUTPUT_START = opt_dims.COMP_OUTPUT_START or 13      -- Y-position where output line starts
  local COMP_OUTPUT_END = opt_dims.COMP_OUTPUT_END or 16        -- Y-position where output line ends (non-inverted)
  local COMP_OUTPUT_EXT = opt_dims.COMP_OUTPUT_EXT or 25        -- Y-position for extended output line
  local COMP_INVERT_CENTER = opt_dims.COMP_INVERT_CENTER or 14.5   -- Y-position of inversion circle center
  local COMP_INVERT_RADIUS = opt_dims.COMP_INVERT_RADIUS or 1.5    -- Radius of inversion circle

  local INPUT_SYMBOL_X = opt_dims.INPUT_SYMBOL_X or 5          -- X-position of +/- symbols
  local INPUT_SYMBOL_Y_TOP = opt_dims.INPUT_SYMBOL_Y_TOP or -10    -- Top Y-position of input symbols
  local INPUT_SYMBOL_Y_BOT = opt_dims.INPUT_SYMBOL_Y_BOT or -8     -- Bottom Y-position of input symbols
  local INPUT_SYMBOL_Y_HORIZ = opt_dims.INPUT_SYMBOL_Y_HORIZ or -9   -- Y-position of horizontal line in + symbol

  local INPUT_PIN_Y = opt_dims.INPUT_PIN_Y or -13           -- Y-position where input pins start
  local INPUT_PIN_LENGTH = opt_dims.INPUT_PIN_LENGTH or 5        -- Length of input pin line
  local INPUT_PIN_CIRCLE_OFFSET = opt_dims.INPUT_PIN_CIRCLE_OFFSET or 6 -- Offset to pin circle from start
  local INPUT_PIN_CIRCLE_RADIUS = opt_dims.INPUT_PIN_CIRCLE_RADIUS or 1.0
  local INPUT_EXTENDED_Y = opt_dims.INPUT_EXTENDED_Y or -25      -- Y-position for extended negative input

  local OUTPUT_EXT_Y_START = opt_dims.OUTPUT_EXT_Y_START or 18     -- Y-position where external output starts
  local OUTPUT_EXT_Y_END = opt_dims.OUTPUT_EXT_Y_END or 20       -- Y-position where external output ends
  local OUTPUT_EXT_CIRCLE_Y = opt_dims.OUTPUT_EXT_CIRCLE_Y or 21    -- Y-position of external output circle

  local OUTPUT_DISCONNECTED_X_SIZE = 2                       -- Half-size of X symbol for disconnected output

  local HYST_COORDS = opt_dims.HYST_COORDS or {             -- Hysteresis indicator coordinates
    x = {-2, -2, 2, 2, -2, -2, 2},
    y = {-2.5, -1, -1, 2.5, -1, 1, 1}
  }

  -- =========================
  -- Input validation
  -- =========================

  -- Validate position coordinates
  if type(x0) ~= 'number' or type(y0) ~= 'number' then
    error('x0 and y0 must be numbers')
  end

  -- Validate that all inputs are actually boolean
  if type(isInverted) ~= 'boolean'
  or type(showHyst) ~= 'boolean'
  or type(showOutExt) ~= 'boolean'
  or type(negInputFromModel) ~= 'boolean'
  or type(opt_swapInputs) ~= 'boolean'
  or type(showPosInputCircle) ~= 'boolean'
  or type(opt_disconnectedOut) ~= 'boolean' then
    error('These inputs must be boolean')
  end

  -- =========================
  -- Setup
  -- =========================

  -- Set drawing color for comparator symbol
  Icon:color('event')

  -- =========================
  -- Get helper functions with transformation applied
  -- =========================
  local helpers = _drawCompHelperFunctions(x0, y0, direction, flipped)
  local _rotate = helpers._rotate
  local _lineR = helpers._lineR
  local _circleR = helpers._circleR
  local _drawCompInPin = helpers._drawCompInPin

  -- =========================
  -- Drawing the comparator body
  -- =========================

  -- Draw the main triangular body: vertices at (-13, -13), (0, 13), (13, -13)
  _lineR({-COMP_HALF_WIDTH, 0, COMP_HALF_WIDTH, -COMP_HALF_WIDTH},
         {-COMP_HALF_HEIGHT, COMP_HALF_HEIGHT, -COMP_HALF_HEIGHT, -COMP_HALF_HEIGHT})

  -- Draw disconnected output indicator (X symbol at end of output line)
  if opt_disconnectedOut then
    -- Position X slightly past where inversion circle would be
    local xCenter = COMP_INVERT_CENTER + COMP_INVERT_RADIUS
    local xSize = OUTPUT_DISCONNECTED_X_SIZE
    -- Draw X as two diagonal lines crossing
    _lineR({-xSize, xSize}, {xCenter - xSize, xCenter + xSize})
    _lineR({-xSize, xSize}, {xCenter + xSize, xCenter - xSize})
    _lineR({0, 0}, {COMP_OUTPUT_START, xCenter})
  else
    -- Draw vertical line from apex to output (inside triangle)
    _lineR({0, 0}, {COMP_OUTPUT_END, COMP_OUTPUT_EXT})

    -- Draw the output indicator (inversion circle OR diagonal line)
    if isInverted then
      _circleR(0.0, COMP_INVERT_CENTER, COMP_INVERT_RADIUS)
    else
      _lineR({0, 0}, {COMP_OUTPUT_START, COMP_OUTPUT_END})
    end
  end

  -- =========================
  -- Drawing input symbols
  -- =========================

  -- Calculate x-position for inputs (respects flip and swap)
  --local inputXPos = flipped and -INPUT_SYMBOL_X or INPUT_SYMBOL_X
  local inputXPos = INPUT_SYMBOL_X
  local inputXNeg = -inputXPos

  -- If opt_swapInputs is true, swap the positions where we draw the symbols
  if opt_swapInputs then
    inputXPos, inputXNeg = inputXNeg, inputXPos
  end

  -- Draw the negative input symbol (horizontal line only)
  _lineR({inputXNeg, inputXNeg}, {INPUT_SYMBOL_Y_TOP, INPUT_SYMBOL_Y_BOT})

  -- Draw the positive input symbol (vertical + horizontal lines forming a +)
  _lineR({inputXPos, inputXPos}, {INPUT_SYMBOL_Y_TOP, INPUT_SYMBOL_Y_BOT})
  _lineR({inputXPos - 1.5, inputXPos + 1.5}, {INPUT_SYMBOL_Y_HORIZ, INPUT_SYMBOL_Y_HORIZ})

  -- =========================
  -- Drawing hysteresis indicator (optional)
  -- =========================

  if showHyst then
    -- Draw double-line arrow pattern for hysteresis
    _lineR({HYST_COORDS.x[1], HYST_COORDS.x[2], HYST_COORDS.x[3], HYST_COORDS.x[4]},
           {HYST_COORDS.y[1], HYST_COORDS.y[2], HYST_COORDS.y[3], HYST_COORDS.y[4]})
    _lineR({HYST_COORDS.x[5], HYST_COORDS.x[6], HYST_COORDS.x[7]},
           {HYST_COORDS.y[5], HYST_COORDS.y[6], HYST_COORDS.y[7]})
  end

  -- =========================
  -- Drawing input connections
  -- =========================

  -- Calculate the actual port positions (respects flip but NOT swap - swap only affects symbols)
  --local portXPos = flipped and -INPUT_SYMBOL_X or INPUT_SYMBOL_X
  local portXPos = INPUT_SYMBOL_X
  local portXNeg = -portXPos

  -- If opt_swapInputs is true, swap the physical port positions too
  if opt_swapInputs then
    portXPos, portXNeg = portXNeg, portXPos
  end

  -- Draw connection to the negative terminal
  if negInputFromModel then
    -- Direct connection (line extending to frame for model connection)
    _lineR({portXNeg, portXNeg}, {INPUT_PIN_Y, INPUT_EXTENDED_Y})
  else
    -- Pin with circle (MCU pin connection)
    _drawCompInPin(portXNeg, INPUT_PIN_Y, INPUT_PIN_LENGTH, INPUT_PIN_CIRCLE_OFFSET, INPUT_PIN_CIRCLE_RADIUS)
  end

  -- Draw connection to the positive terminal (circle for MCU pin, extends to frame for model)
  _drawCompInPin(portXPos, INPUT_PIN_Y, INPUT_PIN_LENGTH, INPUT_PIN_CIRCLE_OFFSET, INPUT_PIN_CIRCLE_RADIUS, showPosInputCircle)

  -- =========================
  -- Drawing external output (optional)
  -- =========================

  if showOutExt then
    -- Draw line from center output to side with circle
    _lineR({0, inputXNeg, inputXNeg}, {OUTPUT_EXT_Y_START, OUTPUT_EXT_Y_START, OUTPUT_EXT_Y_END})
    _circleR(inputXNeg, OUTPUT_EXT_CIRCLE_Y, INPUT_PIN_CIRCLE_RADIUS)
  end
end

function iL.drawComparator(isInverted, showHyst, showOutExt, negativeIn)
  local direction = Block:get('Direction')
  local flipped   = Block:get('Flipped')
  local comparatorCenterCoords = {0, 0}
  local optConfig = {
    isInverted = isInverted,
    negInputFromModel = negativeIn,
    showHyst = showHyst,
    showOutExt = showOutExt,
    showPosInputCircle = true  -- positive terminal always connected to external MCU pin
  }
  _drawComparatorAt(comparatorCenterCoords, optConfig,direction,flipped)
end

-- draw two comparators stacked vertically for TI dual comparator modules
-- compHighSideConfig: table with {isInverted, negativeIn, disconnectedOut} for high-side comparator
-- compLowSideConfig: table with {isInverted, negativeIn, disconnectedOut} for low-side comparator
-- showHyst: boolean for hysteresis indicator (shared by both)
-- showOutExt: boolean for external output (shared by both)
function iL.drawDualComparatorTI(compHighSideConfig, compLowSideConfig, showHyst, showOutExt)
  local spacing = 30  -- center-to-center distance between the two comparators
  local direction = Block:get('Direction')
  local flipped   = Block:get('Flipped')

  -- Positioning adjustments for TI dual comparator layout
  local PIN_SHORTEN = 3       -- amount to shorten vertical pin extensions (output and negative input)
  local CENTER_SHIFT = 5      -- distance to shift comparators toward center along stacking axis
  local POS_PIN_LENGTH = 3    -- length of positive input pins (shorter than default 5)

  -- Comparator dimensions with shortened pins for tighter dual layout
  -- Only override dimensions that differ from defaults
  local dims = {
    INPUT_PIN_LENGTH = POS_PIN_LENGTH,            -- shortened positive input pin length (default is 5)
    COMP_OUTPUT_END = 30,                         -- dual comparator width increased
    COMP_OUTPUT_EXT = 20 - PIN_SHORTEN,           -- shortened output pin (default is 25)
    INPUT_EXTENDED_Y = -30 - PIN_SHORTEN,         -- shortened negative input pin
  }

  -- Calculate key positions for connecting positive terminals
  local INPUT_PIN_Y = -13  -- default value for where input pins start
  local POS_PIN_END_Y = INPUT_PIN_Y - POS_PIN_LENGTH  -- where shortened positive pins end
  local COMMON_LINE_Y = POS_PIN_END_Y - 5                   -- y-position of horizontal connecting line
  local CONN_CIRCLE_RADIUS = 1.0  -- default radius for connection circle

  -- Calculate positions based on block direction and determine input swap configuration
  -- Goal: positive terminals always face each other (toward the center line between comparators)
  local swap1, swap2
  local x1, y1, x2, y2
  local halfSpacing = spacing / 2

  if direction == 0.0 then
    -- 90° CCW: comparators stack horizontally (side by side)
    x1, y1 = halfSpacing - CENTER_SHIFT, PIN_SHORTEN
    x2, y2 = -halfSpacing - CENTER_SHIFT, PIN_SHORTEN
    swap1, swap2 = true, false
  elseif direction == 0.25 then
    -- 180°: comparators stack vertically (one above the other)
    x1, y1 = -PIN_SHORTEN, halfSpacing - CENTER_SHIFT
    x2, y2 = -PIN_SHORTEN, -halfSpacing - CENTER_SHIFT
    swap1, swap2 = true, false
  elseif direction == 0.5 then
    -- 90° CW: comparators stack horizontally (side by side)
    x1, y1 = -halfSpacing + CENTER_SHIFT, -PIN_SHORTEN
    x2, y2 = halfSpacing + CENTER_SHIFT, -PIN_SHORTEN
    swap1, swap2 = true, false
  else  -- direction == 0.75 (base orientation)
    -- 0°: comparators stack vertically (one above the other)
    x1, y1 = PIN_SHORTEN, -halfSpacing + CENTER_SHIFT
    x2, y2 = PIN_SHORTEN, halfSpacing + CENTER_SHIFT
    swap1, swap2 = true, false
  end

  -- Draw both comparators (positive inputs connected together internally, not to individual MCU pins)
  local highSideOptConfig = {
    isInverted = compHighSideConfig.isInverted,
    negInputFromModel = compHighSideConfig.negativeIn,
    showHyst = showHyst,
    showOutExt = showOutExt,
    swapInputs = swap1,
    dims = dims,
    showPosInputCircle = false,  -- positive inputs wired together
    disconnectedOut = compHighSideConfig.disconnectedOut
  }
  local lowSideOptConfig = {
    isInverted = compLowSideConfig.isInverted,
    negInputFromModel = compLowSideConfig.negativeIn,
    showHyst = showHyst,
    showOutExt = showOutExt,
    swapInputs = swap2,
    dims = dims,
    showPosInputCircle = false,  -- positive inputs wired together
    disconnectedOut = compLowSideConfig.disconnectedOut
  }
  _drawComparatorAt({x1, y1}, highSideOptConfig,direction,flipped)
  _drawComparatorAt({x2, y2}, lowSideOptConfig,direction,flipped)

  -- Get helper functions for drawing connecting lines between positive terminals
  local helpers = _drawCompHelperFunctions(0, 0, direction, flipped)
  local _lineR = helpers._lineR
  local _circleR = helpers._circleR

  -- Draw connections between positive terminals in local coordinates (before rotation)
  -- Calculate X positions for horizontal line connecting the two positive pins
  local INPUT_SYMBOL_X = 5  -- default X-position of +/- symbols
  local horizX1 = -(halfSpacing - INPUT_SYMBOL_X) - CENTER_SHIFT  -- evaluates to -13
  local horizX2 = (halfSpacing - INPUT_SYMBOL_X) - CENTER_SHIFT   -- evaluates to 7
  local horizY = POS_PIN_END_Y + 2                                      -- evaluates to -14

  -- Vertical drop line and circle position
  local vertX = -CENTER_SHIFT                                           -- evaluates to -3
  local dropY = POS_PIN_END_Y - PIN_SHORTEN                             -- evaluates to -19

  -- Draw horizontal line connecting the two positive terminal pins
  _lineR({horizX1, horizX2}, {horizY, horizY})

  -- Draw vertical drop line from center of horizontal line to connection point
  _lineR({vertX, vertX}, {dropY, horizY})

  -- Draw connection circle where external positive input attaches
  _circleR(vertX, dropY, CONN_CIRCLE_RADIUS)
end

-- Used for blocks that will still compile, but we strongly encourage
-- not using any more.
function iL.drawDeprecate(c)
  local color_d = c or {128, 128, 128}  -- default to gray

  local frame = Block:get('Frame')
  local xLeft = frame[1]
  local xTot = frame[3]
  local yUp = frame[2]
  local yTot = frame[4]

  Icon:color(color_d)
  Icon:line({xLeft + 10, xLeft + xTot - 10}, {yUp + yTot - 10, yUp + 10})
end

function iL.drawRemove(c)
  local color_d = c or {128, 128, 128}  -- default to gray

  local frame = Block:get('Frame')
  local xLeft = frame[1]
  local xTot = frame[3]
  local yUp = frame[2]
  local yTot = frame[4]

  Icon:color(color_d)
  Icon:line({xLeft + 10, xLeft + xTot - 10}, {yUp + yTot - 10, yUp + 10})
  Icon:line({xLeft + xTot - 10, xLeft + 10}, {yUp + yTot - 10, yUp + 10})
end

function iL.drawOpamp(mode, posIn, out)
  local direction = Block:get('Direction')
  local flipped = Block:get('Flipped')
  local gain = 1

  local opampMode = {'standalone', 'follower', 'pga'}  -- must match block dialog -- this is weirdly brittle, all dialogs must define these as 1, 2, 3
  local m = opampMode[mode]

  if flipped then
    gain = -1
  end
  Icon:color('event')
  if direction == 0.0 then
    Icon:line({-13, 0, 13, -13}, {-13, 13, -13, -13})
    Icon:line({-5, -5}, {-10, -8})
    Icon:line({5, 5}, {-10, -8})
    Icon:line({4 * gain, 6 * gain}, {-9, -9})
    if posIn == 2 then
      Icon:line({5 * gain, 5 * gain}, {-13, -40})
    else
      Icon:line({5 * gain, 5 * gain}, {-13, -30})
      Icon:circle(5 * gain, -31, 1)
    end
    Icon:line({0, 0}, {13, 30})
    if out == 1 then
      Icon:circle(0, 31, 1)
    else
      Icon:line({2, 0, -2}, {27, 30, 27})
    end
  elseif direction == 0.25 then
    Icon:line({13, -13, 13, 13}, {-13, 0, 13, -13})
    Icon:line({10, 8}, {-5, -5})
    Icon:line({10, 8}, {5, 5})
    Icon:line({9, 9}, {4 * gain, 6 * gain})
    if posIn == 2 then
      Icon:line({13, 40}, {5 * gain, 5 * gain})
    else
      Icon:line({13, 30}, {5 * gain, 5 * gain})
      Icon:circle(31, 5 * gain, 1)
    end
    Icon:line({-13, -30}, {0, 0})
    if out == 1 then
      Icon:circle(-31, 0, 1)
    else
      Icon:line({-27, -30, -27}, {2, 0, -2})
    end
  elseif direction == 0.5 then
    Icon:line({13, 0, -13, 13}, {13, -13, 13, 13})
    Icon:line({-5, -5}, {10, 8})
    Icon:line({5, 5}, {10, 8})
    Icon:line({-4 * gain, -6 * gain}, {9, 9})
    if posIn == 2 then
      Icon:line({-5 * gain, -5 * gain}, {13, 40})
    else
      Icon:line({-5 * gain, -5 * gain}, {13, 30})
      Icon:circle(-5 * gain, 31, 1)
    end
    Icon:line({0, 0}, {-13, -30})
    if out == 1 then
      Icon:circle(0, -31, 1)
    else
      Icon:line({2, 0, -2}, {-27, -30, -27})
    end
  elseif direction == 0.75 then
    Icon:line({-13, 13, -13, -13}, {13, 0, -13, 13})
    Icon:line({-10, -8}, {5, 5})
    Icon:line({-10, -8}, {-5, -5})
    Icon:line({-9, -9}, {-4 * gain, -6 * gain})
    if posIn == 2 then
      Icon:line({-13, -40}, {-5 * gain, -5 * gain})
    else
      Icon:line({-13, -30}, {-5 * gain, -5 * gain})
      Icon:circle(-31, -5 * gain, 1)
    end
    Icon:line({13, 30}, {0, 0})
    if out == 1 then
      Icon:circle(31, 0, 1)
    else
      Icon:line({27, 30, 27}, {2, 0, -2})
    end
  end

  if m == 'follower' then
    if direction == 0.0 then
      Icon:line({0, 0, -20 * gain, -20 * gain, -5 * gain, -5 * gain}, {13, 20, 20, -20, -20, -13})
    elseif direction == 0.25 then
      Icon:line({-13, -20, -20, 20, 20, 13}, {0, 0, -20 * gain, -20 * gain, -5 * gain, -5 * gain})
    elseif direction == 0.5 then
      Icon:line({0, 0, 20 * gain, 20 * gain, 5 * gain, 5 * gain}, {-13, -20, -20, 20, 20, 13})
    elseif direction == 0.75 then
      Icon:line({13, 20, 20, -20, -20, -13}, {0, 0, 20 * gain, 20 * gain, 5 * gain, 5 * gain})
    end
  elseif m == 'standalone' then
    if direction == 0.0 then
      Icon:line({-5 * gain, -5 * gain}, {-13, -30})
      Icon:circle(-5 * gain, -31, 1)
    elseif direction == 0.25 then
      Icon:line({13, 30}, {-5 * gain, -5 * gain})
      Icon:circle(31, -5 * gain, 1)
    elseif direction == 0.5 then
      Icon:line({5 * gain, 5 * gain}, {13, 30})
      Icon:circle(5 * gain, 31, 1)
    elseif direction == 0.75 then
      Icon:line({-13, -30}, {5 * gain, 5 * gain})
      Icon:circle(-31, 5 * gain, 1)
    end
  elseif m == 'pga' then
    if direction == 0.0 then
      Icon:line({0, -8 * gain}, {20, 20})
      Icon:line({-8 * gain, -8 * gain}, {18, 22})
      Icon:line({-8 * gain, -14 * gain}, {18, 18})
      Icon:line({-8 * gain, -14 * gain}, {22, 22})
      Icon:line({-14 * gain, -14 * gain}, {18, 22})
      Icon:line({-14 * gain, -18 * gain, -18 * gain, -5 * gain, -5 * gain}, {20, 20, -20, -20, -13})
      Icon:line({-18 * gain, -22 * gain}, {20, 20})
      Icon:line({-22 * gain, -22 * gain}, {18, 22})
      Icon:line({-22 * gain, -28 * gain}, {18, 18})
      Icon:line({-22 * gain, -28 * gain}, {22, 22})
      Icon:line({-28 * gain, -28 * gain}, {18, 22})
      Icon:line({-28 * gain, -32 * gain}, {20, 20})
      Icon:line({-32 * gain, -32 * gain}, {18, 22})
      Icon:line({-34 * gain, -34 * gain}, {19, 21})
      Icon:circle(-18 * gain, 20, 1)
      Icon:circle(0, 20, 1)
    elseif direction == 0.25 then
      Icon:line({-20, -20}, {0, -8 * gain})
      Icon:line({-18, -22}, {-8 * gain, -8 * gain})
      Icon:line({-18, -18}, {-8 * gain, -14 * gain})
      Icon:line({-22, -22}, {-8 * gain, -14 * gain})
      Icon:line({-18, -22}, {-14 * gain, -14 * gain})
      Icon:line({-20, -20, 20, 20, 13}, {-14 * gain, -18 * gain, -18 * gain, -5 * gain, -5 * gain})
      Icon:line({-20, -20}, {-18 * gain, -22 * gain})
      Icon:line({-18, -22}, {-22 * gain, -22 * gain})
      Icon:line({-18, -18}, {-22 * gain, -28 * gain})
      Icon:line({-22, -22}, {-22 * gain, -28 * gain})
      Icon:line({-18, -22}, {-28 * gain, -28 * gain})
      Icon:line({-20, -20}, {-28 * gain, -32 * gain})
      Icon:line({-18, -22}, {-32 * gain, -32 * gain})
      Icon:line({-19, -21}, {-34 * gain, -34 * gain})
      Icon:circle(-20, -18 * gain, 1)
      Icon:circle(-20, 0, 1)
    elseif direction == 0.5 then
      Icon:line({0, 8 * gain}, {-20, -20})
      Icon:line({8 * gain, 8 * gain}, {-18, -22})
      Icon:line({8 * gain, 14 * gain}, {-18, -18})
      Icon:line({8 * gain, 14 * gain}, {-22, -22})
      Icon:line({14 * gain, 14 * gain}, {-18, -22})
      Icon:line({14 * gain, 18 * gain, 18 * gain, 5 * gain, 5 * gain}, {-20, -20, 20, 20, 13})
      Icon:line({18 * gain, 22 * gain}, {-20, -20})
      Icon:line({22 * gain, 22 * gain}, {-18, -22})
      Icon:line({22 * gain, 28 * gain}, {-18, -18})
      Icon:line({22 * gain, 28 * gain}, {-22, -22})
      Icon:line({28 * gain, 28 * gain}, {-18, -22})
      Icon:line({28 * gain, 32 * gain}, {-20, -20})
      Icon:line({32 * gain, 32 * gain}, {-18, -22})
      Icon:line({34 * gain, 34 * gain}, {-19, -21})
      Icon:circle(18 * gain, 20, 1)
      Icon:circle(0, -20, 1)
    elseif direction == 0.75 then
      Icon:line({20, 20}, {0, 8 * gain})
      Icon:line({18, 22}, {8 * gain, 8 * gain})
      Icon:line({18, 18}, {8 * gain, 14 * gain})
      Icon:line({22, 22}, {8 * gain, 14 * gain})
      Icon:line({18, 22}, {14 * gain, 14 * gain})
      Icon:line({20, 20, -20, -20, -13}, {14 * gain, 18 * gain, 18 * gain, 5 * gain, 5 * gain})
      Icon:line({20, 20}, {18 * gain, 22 * gain})
      Icon:line({18, 22}, {22 * gain, 22 * gain})
      Icon:line({18, 18}, {22 * gain, 28 * gain})
      Icon:line({22, 22}, {22 * gain, 28 * gain})
      Icon:line({18, 22}, {28 * gain, 28 * gain})
      Icon:line({20, 20}, {28 * gain, 32 * gain})
      Icon:line({18, 22}, {32 * gain, 32 * gain})
      Icon:line({19, 21}, {34 * gain, 34 * gain})
      Icon:circle(20, 18 * gain, 1)
      Icon:circle(20, 0, 1)
    end
  end
end

return iL
