var package = {}
var fd = use('fd')
var toml = use('toml')
var runtime = use('runtime')
var link = use('link')

var global_shop_path = runtime.shop_path

// Convert package name to a safe directory name
// For absolute paths (local packages), replace / with _ 
// For remote packages, keep slashes as they use nested directories
function safe_package_path(pkg) {
  if (!pkg) return pkg
  if (starts_with(pkg, '/'))
    return replace(replace(pkg, '/', '_'), '@', '_')
  return replace(pkg, '@', '_')
}

function get_path(name)
{
  // If name is null, return the current project directory
  if (!name)
    return fd.realpath('.')
  // If name is already an absolute path, use it directly
  if (starts_with(name, '/'))
    return name

  // Check if this package is linked - if so, use the link target directly
  // This avoids symlink-related issues with file reading
  var link_target = link.get_target(name)
  if (link_target) {
    // If link target is a local path, use it directly
    if (starts_with(link_target, '/'))
      return link_target
    // Otherwise it's another package name, resolve that
    return global_shop_path + '/packages/' + replace(replace(link_target, '/', '_'), '@', '_')
  }

  // Remote packages use nested directories, so don't transform slashes
  return global_shop_path + '/packages/' + replace(name, '@', '_')
}

var config_cache = {}

package.load_config = function(name)
{
  var cache_key = name || '_project_'
  if (config_cache[cache_key]) return config_cache[cache_key]

  var config_path = get_path(name) + '/cell.toml'

  if (!fd.is_file(config_path)) {
    print(`${config_path} does not exist`); disrupt
  }

  var content = text(fd.slurp(config_path))
  if (!content || length(trim(content)) == 0)
    return {}

  var result = toml.decode(content)
  if (!result) {
    print(`TOML decode returned null for ${config_path}`)
    return {}
  }

  config_cache[cache_key] = result
  return result
}

package.save_config = function(name, config)
{
  var config_path = get_path(name) + '/cell.toml'
  fd.slurpwrite(config_path, stone(blob(toml.encode(config))))
}

package.dependencies = function(name)
{
  return package.load_config(name).dependencies
}

package.find_alias = function(name, locator)
{
  var config = package.load_config(name)
  if (!config.dependencies) return null
  
  var found = null
  arrfor(array(config.dependencies), function(alias) {
    if (config.dependencies[alias] == locator) found = alias
  })
  return found
}

package.alias_to_package = function(name, alias)
{
  var config = package.load_config(name)
  if (!config.dependencies) return null
  return config.dependencies[alias]
}

// alias is optional
package.add_dependency = function(name, locator, alias)
{
  var _alias = alias == null ? locator : alias
  var config = package.load_config(name)
  if (!config.dependencies) config.dependencies = {}
  config.dependencies[_alias] = locator
  package.save_config(name, config)
}

// locator can be a locator or alias
package.remove_dependency = function(name, locator)
{
  var config = package.load_config(name)
  if (!config.dependencies) return
  
  var alias = null
  if (config.dependencies[locator])
    delete config.dependencies[locator]
  else {
    alias = package.find_alias(name, locator)
    if (alias)
      delete config.dependencies[alias]
  }
  package.save_config(name, config)
}

package.find_package_dir = function(file)
{
  var absolute = fd.realpath(file)
  
  var dir = absolute
  if (fd.is_file(dir))
    dir = fd.dirname(dir)
  
  var toml_path = null
  while (dir && length(dir) > 0) {
    toml_path = dir + '/cell.toml'
    if (fd.is_file(toml_path)) {
      return dir
    }
    dir = fd.dirname(dir)
  }
  
  return null
}

// For a given package,
// checks for an alias in path, and returns
// { package, path }
// so package + path is the full path
// Returns null if no alias is found for the given path
package.split_alias = function(name, path)
{
  if (!path || length(path) == 0) {
    return null
  }

  var parts = array(path, '/')
  var first_part = parts[0]

  var _split = function() {
    var config = package.load_config(name)
    if (!config) return null

    var deps = config.dependencies
    var dep_locator = null
    var remaining_path = null
    if (deps && deps[first_part]) {
      dep_locator = deps[first_part]
      remaining_path = text(array(parts, 1), '/')
      return { package: dep_locator, path: remaining_path }
    }
    return null
  } disruption {
    return null
  }
  return _split()
}

package.gather_dependencies = function(name)
{
  var all_deps = {}
  var visited = {}
  
  function gather_recursive(pkg_name) {
    if (visited[pkg_name]) return
    visited[pkg_name] = true
    
    var deps = package.dependencies(pkg_name)
    if (!deps) return
    
    arrfor(array(deps), function(alias) {
      var locator = deps[alias]
      if (!all_deps[locator]) {
        all_deps[locator] = true
        gather_recursive(locator)
      }
    })
  }
  
  gather_recursive(name)
  return array(all_deps)
}

package.list_files = function(pkg) {
  var dir = get_path(pkg)
  
  var files = []
  
  var walk = function(current_dir, current_prefix) {
    var list = fd.readdir(current_dir)
    if (!list) return

    var i = 0
    var item = null
    var full_path = null
    var rel_path = null
    var st = null
    for (i = 0; i < length(list); i++) {
      item = list[i]
      if (item == '.' || item == '..') continue
      if (starts_with(item, '.')) continue

      // Skip build directories in root

      full_path = current_dir + "/" + item
      rel_path = current_prefix ? current_prefix + "/" + item : item

      st = fd.stat(full_path)
      if (st.isDirectory) {
        walk(full_path, rel_path)
      } else {
        push(files, rel_path)
      }
    }
  }
  
  if (fd.is_dir(dir)) {
    walk(dir, "")
  }
  return files
}

package.list_modules = function(name) {
  var files = package.list_files(name)
  var modules = []
  var i = 0
  var stem = null
  for (i = 0; i < length(files); i++) {
    if (ends_with(files[i], '.cm')) {
      push(modules, text(files[i], 0, -3))
    }
  }
  var c_files = package.get_c_files(name, null, true)
  for (i = 0; i < length(c_files); i++) {
    stem = ends_with(c_files[i], '.cpp') ? text(c_files[i], 0, -4) : text(c_files[i], 0, -2)
    if (find(modules, function(m) { return m == stem }) == null)
      push(modules, stem)
  }
  return modules
}

package.list_programs = function(name) {
  var files = package.list_files(name)
  var programs = []
  var i = 0
  for (i = 0; i < length(files); i++) {
    if (ends_with(files[i], '.ce')) {
      push(programs, text(files[i], 0, -3))
    }
  }
  return programs
}

// Get flags from cell.toml for a package
// flag_type is 'CFLAGS' or 'LDFLAGS'
// target is optional (e.g., 'macos_arm64', 'playdate')
// Returns an array of flag strings
package.get_flags = function(name, flag_type, target) {
  var config = package.load_config(name)
  var comp = config.compilation
  var flags = []
  var base = null
  var target_flags = null

  if (!comp) return flags

  // Base flags
  if (comp[flag_type]) {
    base = comp[flag_type]
    flags = array(flags, filter(array(base, /\s+/), function(f) { return length(f) > 0 }))
  }

  // Target-specific flags
  if (target && comp[target] && comp[target][flag_type]) {
    target_flags = comp[target][flag_type]
    flags = array(flags, filter(array(target_flags, /\s+/), function(f) { return length(f) > 0 }))
  }

  return flags
}

// Get all C files for a package, handling target-specific variants
// Excludes main.c for dynamic builds (when exclude_main is true)
// Handles patterns like fd.c vs fd_playdate.c
package.get_c_files = function(name, target, exclude_main) {
  var toolchains = use('toolchains')
  var known_targets = array(toolchains)
  var files = package.list_files(name)
  
  // Group files by their base name (without target suffix)
  var groups = {} // base_key -> { generic: file, variants: { target: file } }
  
  var i = 0
  var file = null
  var ext = null
  var base = null
  var name_part = null
  var dir_part = null
  var dir = null
  var is_variant = null
  var variant_target = null
  var generic_name = null
  var t = 0
  var suffix = null
  var group_key = null
  for (i = 0; i < length(files); i++) {
    file = files[i]
    if (!ends_with(file, '.c') && !ends_with(file, '.cpp')) continue

    ext = ends_with(file, '.cpp') ? '.cpp' : '.c'
    base = text(file, 0, -length(ext))
    name_part = fd.basename(base)
    dir_part = fd.dirname(base)
    dir = (dir_part && dir_part != '.') ? dir_part + '/' : ''

    // Check for target suffix
    is_variant = false
    variant_target = null
    generic_name = name_part

    for (t = 0; t < length(known_targets); t++) {
      suffix = '_' + known_targets[t]
      if (ends_with(name_part, suffix)) {
        is_variant = true
        variant_target = known_targets[t]
        generic_name = text(name_part, 0, -length(suffix))
        break
      }
    }
    
    group_key = dir + generic_name + ext
    if (!groups[group_key]) {
      groups[group_key] = { generic: null, variants: {} }
    }
    
    if (is_variant) {
      groups[group_key].variants[variant_target] = file
    } else {
      groups[group_key].generic = file
    }
  }
  
  // Select appropriate file from each group
  var result = []
  arrfor(array(groups), function(key) {
    var group = groups[key]
    var selected = null
    var basename = null

    // Prefer target-specific variant if available
    if (target && group.variants[target]) {
      selected = group.variants[target]
    } else if (group.generic) {
      selected = group.generic
    }

    if (selected) {
      // Skip main.c if requested
      if (exclude_main) {
        basename = fd.basename(selected)
        if (basename == 'main.c' || starts_with(basename, 'main_')) return
      }
      push(result, selected)
    }
  })

  // Exclude src/ files (support files, not modules)
  var sources = package.get_sources(name)
  if (length(sources) > 0) {
    result = filter(result, function(f) {
      return find(sources, function(s) { return s == f }) == null
    })
  }

  return result
}

// Get support source files: C files in src/ directories (not modules)
package.get_sources = function(name) {
  var files = package.list_files(name)
  return filter(files, function(f) {
    return (ends_with(f, '.c') || ends_with(f, '.cpp')) && starts_with(f, 'src/')
  })
}

// Get the absolute path for a package
package.get_dir = function(name) {
  return get_path(name)
}

return package
