--[[
  画像一括取り込み＆一括リサイズ (OBS スクリプト)
  ─────────────────────────────────────────────
  ・フォルダ内の画像をまとめて「画像ソース」として現在のシーンに追加
  ・縦横比を保ったまま「画面内 10〜100%」の枠に収まるよう一括リサイズ
    （バウンディングボックス方式なので、どんな画像サイズでも画面外にはみ出さない）
  ・配置は 中央重ね / ずらし / グリッド から選択
  ・既に取り込み済みの画像ソースへの「%だけ再適用」も可能

  導入: OBS メニュー「ツール > スクリプト」→「＋」→ このファイルを選択

  Version: 1.0.0 / License: MIT / https://haishindonya.jp/plugins/obs-bulk-image-import
]]

obs = obslua

-- ── 設定値（スクリプト画面から入力） ──────────────────────────────
local folder    = ""
local file_list = {}          -- 個別指定した画像のフルパス一覧
local pct       = 50          -- 画面内サイズ % (10〜100)
local placement = "cascade"   -- center / cascade / grid

local IMAGE_EXTS = {
  png = true, jpg = true, jpeg = true, gif = true,
  bmp = true, tga = true, psd = true, webp = true,
}

-- ── ヘルパー ─────────────────────────────────────────────────────
local function log(msg) obs.script_log(obs.LOG_INFO, "[画像一括取り込み] " .. msg) end

local function canvas_size()
  local ovi = obs.obs_video_info()
  if obs.obs_get_video_info(ovi) then
    return ovi.base_width, ovi.base_height
  end
  return 1920, 1080
end

local function is_image_file(name)
  local ext = name:match("%.([^%.]+)$")
  if not ext then return false end
  return IMAGE_EXTS[ext:lower()] == true
end

-- フォルダ内の画像ファイル一覧（名前順）
local function list_images(dirpath)
  local files = {}
  local dir = obs.os_opendir(dirpath)
  if dir == nil then return files end
  local entry
  repeat
    entry = obs.os_readdir(dir)
    if entry and not entry.directory and is_image_file(entry.d_name) then
      table.insert(files, entry.d_name)
    end
  until not entry
  obs.os_closedir(dir)
  table.sort(files)
  return files
end

-- 既存ソース名と被らない名前を作る
local function unique_name(base)
  local name = base
  local i = 2
  while true do
    local s = obs.obs_get_source_by_name(name)
    if s == nil then return name end
    obs.obs_source_release(s)
    name = base .. " (" .. i .. ")"
    i = i + 1
  end
end

-- シーンアイテムに「%枠に収める」バウンディングボックスを設定
local function apply_bounds(item, bw, bh, cx, cy)
  obs.obs_sceneitem_set_bounds_type(item, obs.OBS_BOUNDS_SCALE_INNER)
  obs.obs_sceneitem_set_bounds_alignment(item, 0)          -- 枠内センター
  obs.obs_sceneitem_set_alignment(item, 0)                 -- 位置基準もセンター
  local b = obs.vec2()
  b.x = bw; b.y = bh
  obs.obs_sceneitem_set_bounds(item, b)
  local p = obs.vec2()
  p.x = cx; p.y = cy
  obs.obs_sceneitem_set_pos(item, p)
end

-- n 個分の配置座標（中心点）を作る
local function make_positions(n, cw, ch, bw, bh, mode)
  local pos = {}
  if mode == "center" then
    for i = 1, n do pos[i] = { x = cw / 2, y = ch / 2 } end

  elseif mode == "grid" then
    local cols = math.ceil(math.sqrt(n))
    local rows = math.ceil(n / cols)
    for i = 1, n do
      local c = (i - 1) % cols
      local r = math.floor((i - 1) / cols)
      pos[i] = {
        x = cw * (c + 0.5) / cols,
        y = ch * (r + 0.5) / rows,
      }
    end

  else -- cascade（40px ずつ右下へずらす。枠が画面から出ない範囲で折り返し）
    local step  = 40
    local minx, maxx = bw / 2, cw - bw / 2
    local miny, maxy = bh / 2, ch - bh / 2
    local x, y = math.min(minx + 40, maxx), math.min(miny + 40, maxy)
    for i = 1, n do
      pos[i] = { x = x, y = y }
      x = x + step
      y = y + step
      if x > maxx or y > maxy then         -- 折り返し
        x = math.min(minx + 40, maxx)
        y = math.min(miny + 40, maxy)
      end
    end
  end
  return pos
end

-- ── 取り込み共通処理（フルパスの配列を受け取る） ──────────────────
local function import_paths(paths)
  if #paths == 0 then return false end

  local scene_src = obs.obs_frontend_get_current_scene()
  if scene_src == nil then
    log("エラー: 現在のシーンを取得できません")
    return false
  end
  local scene = obs.obs_scene_from_source(scene_src)

  local cw, ch = canvas_size()
  local bw, bh = cw * pct / 100, ch * pct / 100
  local positions = make_positions(#paths, cw, ch, bw, bh, placement)

  local added = 0
  for i, fullpath in ipairs(paths) do
    local fname = fullpath:match("([^/\\]+)$") or fullpath
    local base  = fname:gsub("%.[^%.]+$", "")   -- 拡張子を外した名前

    local settings = obs.obs_data_create()
    obs.obs_data_set_string(settings, "file", fullpath)
    local src = obs.obs_source_create(
      "image_source", unique_name(base), settings, nil)
    obs.obs_data_release(settings)

    if src ~= nil then
      local item = obs.obs_scene_add(scene, src)
      if item ~= nil then
        apply_bounds(item, bw, bh, positions[i].x, positions[i].y)
        added = added + 1
      end
      obs.obs_source_release(src)
    end
  end

  obs.obs_source_release(scene_src)
  log(string.format(
    "完了: %d 枚を取り込み、画面内 %d%% に収めました（配置: %s）",
    added, pct, placement))
  return true
end

-- ── メイン①-a：フォルダ丸ごと取り込み ────────────────────────────
local function import_clicked(props, p)
  if folder == "" then
    log("エラー: 画像フォルダを選択してください")
    return false
  end

  local files = list_images(folder)
  if #files == 0 then
    log("エラー: フォルダに画像が見つかりません（対応: png/jpg/gif/bmp/tga/psd/webp）")
    return false
  end

  local paths = {}
  for _, fname in ipairs(files) do
    table.insert(paths, folder .. "/" .. fname)
  end
  return import_paths(paths)
end

-- ── メイン①-b：個別指定した画像だけ取り込み ──────────────────────
local function import_selected_clicked(props, p)
  if #file_list == 0 then
    log("エラー: 「個別指定リスト」に画像を追加してください（＋ボタンで複数選択できます）")
    return false
  end

  local paths = {}
  for _, fullpath in ipairs(file_list) do
    local fname = fullpath:match("([^/\\]+)$") or fullpath
    if is_image_file(fname) and obs.os_file_exists(fullpath) then
      table.insert(paths, fullpath)
    else
      log("スキップ（非対応 or 見つからない）: " .. fullpath)
    end
  end
  if #paths == 0 then
    log("エラー: 取り込める画像がリストにありません")
    return false
  end
  return import_paths(paths)
end

-- ── メイン②：既存の画像ソースへ % を再適用 ──────────────────────
local function resize_clicked(props, p)
  local scene_src = obs.obs_frontend_get_current_scene()
  if scene_src == nil then
    log("エラー: 現在のシーンを取得できません")
    return false
  end
  local scene = obs.obs_scene_from_source(scene_src)

  local cw, ch = canvas_size()
  local bw, bh = cw * pct / 100, ch * pct / 100

  local count = 0
  local items = obs.obs_scene_enum_items(scene)
  if items ~= nil then
    for _, item in ipairs(items) do
      local src = obs.obs_sceneitem_get_source(item)
      if src ~= nil and obs.obs_source_get_unversioned_id(src) == "image_source" then
        -- 位置は動かさず、枠サイズだけ更新
        local cur = obs.vec2()
        obs.obs_sceneitem_get_pos(item, cur)
        apply_bounds(item, bw, bh, cur.x, cur.y)
        count = count + 1
      end
    end
    obs.sceneitem_list_release(items)
  end

  obs.obs_source_release(scene_src)
  log(string.format(
    "完了: 現在のシーンの画像ソース %d 件を画面内 %d%% に再設定しました", count, pct))
  return true
end

-- ── スクリプト画面の UI ───────────────────────────────────────────
function script_description()
  return [[<b>画像一括取り込み＆一括リサイズ</b><br/>
取り込み方法は 2 通り：<br/>
・<b>フォルダ丸ごと</b>：フォルダを選んで「▶ フォルダ丸ごと取り込み」<br/>
・<b>選んだ画像だけ</b>：「個別指定リスト」の＋ボタンで画像を選び
（Ctrl/Shift で複数選択可）「▶ 個別指定リストだけ取り込み」<br/><br/>
リサイズは縦横比を保ったまま指定%の枠に収める方式なので、
どのサイズの画像でも画面外にはみ出しません。<br/>
「%だけ後から変えたい」ときは、%を変更して
「既存の画像を一括リサイズ」を押してください。]]
end

function script_properties()
  local props = obs.obs_properties_create()

  obs.obs_properties_add_path(props, "folder",
    "画像フォルダ（丸ごと取り込み用）", obs.OBS_PATH_DIRECTORY, "", nil)

  obs.obs_properties_add_editable_list(props, "files",
    "個別指定リスト（＋で複数選択可）",
    obs.OBS_EDITABLE_LIST_TYPE_FILES,
    "画像ファイル (*.png *.jpg *.jpeg *.gif *.bmp *.tga *.psd *.webp);;すべて (*.*)",
    nil)

  -- 画面内サイズ：プルダウン選択式（100%〜10%・5%刻み）
  local sz = obs.obs_properties_add_list(props, "pct",
    "画面内サイズ", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_INT)
  for v = 100, 10, -5 do
    local label = v .. " %"
    if v == 100 then label = "100 %（画面ぴったり）" end
    if v == 50  then label = "50 %（半分）" end
    obs.obs_property_list_add_int(sz, label, v)
  end

  local pl = obs.obs_properties_add_list(props, "placement",
    "配置", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING)
  obs.obs_property_list_add_string(pl, "ずらして並べる（カスケード）", "cascade")
  obs.obs_property_list_add_string(pl, "中央に重ねる",               "center")
  obs.obs_property_list_add_string(pl, "グリッド状に並べる",         "grid")

  obs.obs_properties_add_button(props, "import_btn",
    "▶ フォルダ丸ごと取り込み（現在のシーン）", import_clicked)
  obs.obs_properties_add_button(props, "import_sel_btn",
    "▶ 個別指定リストだけ取り込み（現在のシーン）", import_selected_clicked)
  obs.obs_properties_add_button(props, "resize_btn",
    "既存の画像を一括リサイズ（現在のシーン）", resize_clicked)

  return props
end

function script_update(settings)
  folder    = obs.obs_data_get_string(settings, "folder")
  pct       = obs.obs_data_get_int(settings, "pct")
  placement = obs.obs_data_get_string(settings, "placement")
  if pct == nil or pct < 10 then pct = 50 end
  if placement == "" then placement = "cascade" end

  -- 個別指定リストの読み取り
  file_list = {}
  local arr = obs.obs_data_get_array(settings, "files")
  if arr ~= nil then
    local n = obs.obs_data_array_count(arr)
    for i = 0, n - 1 do
      local item = obs.obs_data_array_item(arr, i)
      if item ~= nil then
        local v = obs.obs_data_get_string(item, "value")
        if v ~= nil and v ~= "" then table.insert(file_list, v) end
        obs.obs_data_release(item)
      end
    end
    obs.obs_data_array_release(arr)
  end
end

function script_defaults(settings)
  obs.obs_data_set_default_int(settings, "pct", 50)
  obs.obs_data_set_default_string(settings, "placement", "cascade")
end
