#import "/src/cetz.typ": draw, styles

#import "/src/consts.typ": *
#import "/src/core/utils.typ": get-ctx, get-style, is-elmt, set-ctx

#import "participant/default.typ" as shape-default
#import "participant/actor.typ" as shape-actor
#import "participant/boundary.typ" as shape-boundary
#import "participant/control.typ" as shape-control
#import "participant/entity.typ" as shape-entity
#import "participant/database.typ" as shape-database
#import "participant/collections.typ" as shape-collections
#import "participant/queue.typ" as shape-queue
#import "participant/custom.typ" as shape-custom

#let shapes = {
  let from-module(mod) = {
    let p = "participant/default.typ"
    return (mod.name: (
      get-size: mod.get-size,
      render: mod.render,
      default-style: mod.default-style
    ))
  }
  from-module(shape-default)
  from-module(shape-actor)
  from-module(shape-boundary)
  from-module(shape-control)
  from-module(shape-entity)
  from-module(shape-database)
  from-module(shape-collections)
  from-module(shape-queue)
  from-module(shape-custom)
}

#let participant-default-style = (
  fill: auto,
  stroke: black + .5pt,
  from-start: true,
  show-bottom: true,
  show-top: true,
  shape: "participant",
  track: (
    dash: "dashed",
    paint: gray.darken(40%),
    thickness: .5pt
  )
)

#let resolve-style(ctx, par) = {
  let style = styles.resolve(
    ctx.style,
    merge: par.style,
    root: "participant",
    base: participant-default-style
  )
  let shape-style = shapes.at(style.shape, default: (:))
                          .at("default-style", default: (:))
  style = styles.resolve(
    ctx.style,
    merge: style,
    base: shape-style
  )
  return style
}

#let pre-resolve-styles(ctx, elements, participants) = {
  let idx = (:)
  for (i, par) in participants.enumerate() {
    par.insert("resolved-style", resolve-style(ctx, par))
    participants.at(i) = par
    idx.insert(par.name, i)
  }
  for (i, elmt) in elements.enumerate() {
    if type(elmt) == function {
      ctx = elmt(ctx).ctx
    } else if is-elmt(elmt) {
      if elmt.type == par {
        let style = resolve-style(ctx, elmt)
        elements.at(i).insert("resolved-style", style)
        let i = idx.at(elmt.name)
        participants.at(i).resolved-style = style
      }
    }
  }
  return (elements, participants)
}

#let get-size(par) = {
  if par.invisible {
    return (width: 0pt, height: 0pt)
  }
  let style = par.resolved-style
  let func = shapes.at(style.shape).get-size
  return func(par)
}

#let render(par, y: 0, bottom: false) = get-ctx(ctx => {
  let style = resolve-style(ctx, par)
  let func = shapes.at(style.shape).render
  let par = par
  par.resolved-style = style
  func(ctx.x-pos.at(par.i), y, par, bottom)
},)

#let render-lifelines() = get-ctx(ctx => {
  let participants = ctx.participants
  for p in participants.filter(p => not p.invisible) {
    let style = p.resolved-style
    let x = ctx.x-pos.at(p.i)

    // Draw vertical line
    let last-y = 0

    let rects = ()
    let destructions = ()
    let stack = ()

    // Compute lifeline rectangles + destruction positions
    for event in ctx.lifelines.at(p.i).events {
      if event.type == "create" {
        last-y = line.at(1)

      } else if event.type == "enable" {
        if stack.len() == 0 {
          draw.line(
            (x, last-y),
            (x, event.y),
            stroke: style.track
          )
        }
        stack.push(event)
      
      } else if event.type == "disable" or event.type == "destroy" {
        let lvl = 0
        if stack.len() != 0 {
          let e = stack.pop()
          lvl = stack.len()
          rects.push((
            x + lvl * LIFELINE-W / 2,
            e.y,
            event.y,
            e.style
          ))
          last-y = event.y
        }

        if event.type == "destroy" {
          destructions.push((x + lvl * LIFELINE-W / 2, event.y))
        }
      } else if event.type == "delay-start" {
        draw.line(
          (x, last-y),
          (x, event.y),
          stroke: style.track
        )
        last-y = event.y
      } else if event.type == "delay-end" {
        draw.line(
          (x, last-y),
          (x, event.y),
          stroke: event.stroke
        )
        last-y = event.y
      }
    }

    draw.line(
      (x, last-y),
      (x, ctx.y),
      stroke: style.track
    )

    // Draw lifeline rectangles (reverse for bottom to top)
    for rect in rects.rev() {
      let (cx, y0, y1, style) = rect
      let style = get-style("lifeline", style)
      draw.rect(
        (cx - LIFELINE-W / 2, y0),
        (cx + LIFELINE-W / 2, y1),
        ..style
      )
    }

    // Draw lifeline destructions
    for dest in destructions {
      let (cx, cy) = dest
      draw.line((cx - 8, cy - 8), (cx + 8, cy + 8), stroke: COL-DESTRUCTION + 2pt)
      draw.line((cx - 8, cy + 8), (cx + 8, cy - 8), stroke: COL-DESTRUCTION + 2pt)
    }

    // Draw participants (end)
    if style.show-bottom {
      (p.draw)(p, y: ctx.y, bottom: true)
    }
  }
},)