-- ============================================================================
-- INSTALLATION GUIDE
-- ============================================================================
--
-- STEP 1 — Open DaVinci Resolve and open any project
--
-- STEP 2 — Open the console:
--          In the top menu bar, click Workspace > Console
--
-- STEP 3 — Drag and drop this .lua file into the console panel
--          The script will install itself automatically
--
-- STEP 4 — That's it! You can now run it anytime from:
--          Workspace > Scripts > Kevshufilms > copy to clipboard
--
-- ----------------------------------------------------------------------------
-- USAGE (after installation)
--
--   1. Have the playhead over any frame on the Edit page
--   2. Run: Workspace > Scripts > Kevshufilms > copy to clipboard
--   3. Paste into any app (Ctrl+V / Cmd+V)
--
-- ----------------------------------------------------------------------------
-- PLATFORM NOTES
--
--   macOS   — Uses AppleScript (built-in). No extra software needed.
--   Windows — Uses PowerShell (built-in). No extra software needed.
--   Linux   — Requires one of: xclip, xsel, or wl-copy
--             Install via your package manager, e.g.:
--               sudo apt install xclip
--
-- ============================================================================

-- Capture source path at load time (works when loaded via dofile / drag-to-console)
local _SCRIPT_SOURCE = debug.getinfo(1, "S").source

-- ============================================================================
-- UTILITY FUNCTIONS
-- ============================================================================

--- Detect the operating system
function get_os()
    -- Primary detection via Lua JIT
    if jit then
        return jit.os
    end

    -- Fallback detection via environment variables
    if os.getenv("WINDIR") then
        return "Windows"
    end

    -- For Unix-like systems, use uname
    if os.getenv("HOME") then
        local handle = io.popen("uname -s 2>/dev/null", "r")
        if handle then
            local result = handle:read("*a"):gsub("\n", "")
            handle:close()
            if result == "Darwin" then
                return "OSX"
            elseif result == "Linux" then
                return "Linux"
            end
        end
    end

    return "Unknown"
end

--- Get the appropriate temp directory for the OS
function get_temp_dir()
    local os_type = get_os()

    if os_type == "Windows" then
        return os.getenv("TEMP") or os.getenv("TMP") or "C:\\temp"
    elseif os_type == "OSX" then
        -- macOS sets $TMPDIR to the app's sandbox container temp dir for MAS builds;
        -- for standard installs it points to /tmp. Always prefer it over hardcoding.
        local tmpdir = os.getenv("TMPDIR") or "/tmp"
        return tmpdir:gsub("/$", "")  -- strip trailing slash
    else
        return "/tmp"
    end
end

--- Get path separator based on OS
function get_path_separator()
    local os_type = get_os()
    return (os_type == "Windows") and "\\" or "/"
end

--- Validate file was created and has content
function validate_file(file_path)
    local file = io.open(file_path, "rb")
    if not file then
        return false
    end

    local size = file:seek("end")
    file:close()

    return size and size > 0
end

-- ============================================================================
-- INSTALLATION
-- ============================================================================

-- Pure-Lua directory existence check — no subprocess, works inside MAS sandbox.
function lua_dir_exists(path)
    local f, err = io.open(path, "r")
    if f then f:close(); return true end
    return err ~= nil and err:find("[Ii]s a directory") ~= nil
end

-- Pure-Lua file copy — reads/writes via the parent Lua process, so sandbox
-- file-access grants (e.g. drag-and-drop) apply without spawning a subprocess.
function lua_copy_file(src, dst)
    local inf = io.open(src, "rb")
    if not inf then return false end
    local data = inf:read("*a")
    inf:close()
    local outf = io.open(dst, "wb")
    if not outf then return false end
    outf:write(data)
    outf:close()
    return true
end

-- mkdir -p via os.execute, falling back to LuaJIT FFI mkdir on each path
-- component when the MAS sandbox blocks subprocess execution.
function lua_mkdir_p(path, os_type)
    if os_type == "Windows" then
        local result = os.execute('if not exist "' .. path .. '" mkdir "' .. path .. '"')
        return result == 0 or result == true
    end
    local result = os.execute('mkdir -p "' .. path .. '"')
    if result == 0 or result == true then return true end
    -- FFI fallback (LuaJIT, macOS/Linux)
    local ok2, ffi2 = pcall(require, "ffi")
    if not ok2 then return false end
    pcall(ffi2.cdef, [[ int mkdir(const char *pathname, unsigned int mode); ]])
    local current = ""
    for seg in path:gmatch("[^/]+") do
        current = current .. "/" .. seg
        ffi2.C.mkdir(current, 0x1ED)  -- 0755
    end
    return lua_dir_exists(path)
end

-- Returns all install directories applicable to the current OS/environment.
-- On macOS, includes the standard path plus any MAS sandbox containers that exist.
function get_install_dirs()
    local os_type = get_os()
    local dirs = {}

    if os_type == "Windows" then
        dirs[#dirs + 1] = (os.getenv("PROGRAMDATA") or "C:\\ProgramData") ..
            "\\Blackmagic Design\\DaVinci Resolve\\Fusion\\Scripts\\Utility\\Kevshufilms"
    elseif os_type == "Linux" then
        dirs[#dirs + 1] = (os.getenv("HOME") or "") ..
            "/.local/share/DaVinciResolve/Fusion/Scripts/Utility/Kevshufilms"
    else  -- OSX
        local home = os.getenv("HOME") or ""
        -- In the MAS sandbox, HOME is redirected to the container's Data directory
        -- (e.g. ~/Library/Containers/<bundle>/Data). Fusion/Scripts/Utility therefore
        -- lives directly at home/Library/Application Support/Fusion/Scripts/Utility.
        -- In non-MAS Resolve, HOME is the real user home and that path does not exist.
        local mas_util = home .. "/Library/Application Support/Fusion/Scripts/Utility"
        if lua_dir_exists(mas_util) then
            dirs[#dirs + 1] = mas_util .. "/Kevshufilms"
        else
            dirs[#dirs + 1] = home ..
                "/Library/Application Support/Blackmagic Design/DaVinci Resolve" ..
                "/Fusion/Scripts/Utility/Kevshufilms"
        end
    end

    return dirs
end

function is_running_from_install()
    if not (_SCRIPT_SOURCE and _SCRIPT_SOURCE:sub(1, 1) == "@") then
        return false
    end
    local running_from = _SCRIPT_SOURCE:sub(2)
    local sep = get_path_separator()
    for _, dir in ipairs(get_install_dirs()) do
        if running_from == dir .. sep .. "copy to clipboard.lua" then
            return true
        end
    end
    return false
end

function install_self()
    local src_path = nil
    if _SCRIPT_SOURCE and _SCRIPT_SOURCE:sub(1, 1) == "@" then
        src_path = _SCRIPT_SOURCE:sub(2)
    end

    if not src_path then
        print("Copy to Clipboard — Installation")
        print("  Hmm, couldn't find the script file on disk.")
        print('  Make sure "copy to clipboard.lua" is saved, then drag it into the console again.')
        return false
    end

    local os_type = get_os()
    local dirs = get_install_dirs()
    local sep = get_path_separator()
    local any_success = false

    for _, dir in ipairs(dirs) do
        local dest = dir .. sep .. "copy to clipboard.lua"
        lua_mkdir_p(dir, os_type)
        local ok = lua_copy_file(src_path, dest)
        if ok then
            any_success = true
        else
            print("  Failed to install to: " .. dest)
        end
    end

    return any_success
end

-- ============================================================================
-- FRAME EXPORT FUNCTION
-- ============================================================================

--- Get current project, compatible with both Workspace and Fusion consoles
function get_current_project()
    -- Workspace console: resolve is a global object
    if resolve then
        local pm = resolve:GetProjectManager()
        if pm then
            return pm:GetCurrentProject()
        end
    end
    -- Fusion console fallback
    if GetProject then
        return GetProject()
    end
    return nil
end

--- Export current frame with alpha channel preserved
function export_frame_with_alpha(temp_path)
    local project = get_current_project()
    if not project then
        return false, "Could not access DaVinci Resolve project"
    end

    local timeline = project:GetCurrentTimeline()
    if not timeline then
        return false, "No active timeline found. Please select a timeline first."
    end

    -- Export the current frame as PNG
    -- PNG format automatically preserves alpha channels
    local success = project:ExportCurrentFrameAsStill(temp_path)

    if not success then
        return false, "ExportCurrentFrameAsStill() failed"
    end

    -- Verify the file was created
    if not validate_file(temp_path) then
        return false, "Exported file is empty or inaccessible"
    end

    return true, temp_path
end

-- ============================================================================
-- WINDOWS: SILENT PROCESS LAUNCHER (LuaJIT FFI)
-- ============================================================================
-- os.execute() always routes through cmd.exe (a console app), causing a flash.
-- Instead, call CreateProcess() directly with CREATE_NO_WINDOW so no console
-- window is ever created. Falls back to os.execute if FFI is unavailable.

local _win_exec = nil
do
    if get_os() == "Windows" then
        local ok, ffi = pcall(require, "ffi")
        if ok then
            pcall(ffi.cdef, [[
                typedef struct {
                    unsigned long cb;
                    char *lpReserved, *lpDesktop, *lpTitle;
                    unsigned long dwX, dwY, dwXSize, dwYSize;
                    unsigned long dwXCountChars, dwYCountChars, dwFillAttribute, dwFlags;
                    unsigned short wShowWindow, cbReserved2;
                    unsigned char *lpReserved2;
                    void *hStdInput, *hStdOutput, *hStdError;
                } STARTUPINFOA;
                typedef struct {
                    void *hProcess, *hThread;
                    unsigned long dwProcessId, dwThreadId;
                } PROCESS_INFORMATION;
                int CreateProcessA(const char*, char*, void*, void*, int,
                                   unsigned long, void*, const char*,
                                   STARTUPINFOA*, PROCESS_INFORMATION*);
                unsigned long WaitForSingleObject(void*, unsigned long);
                int CloseHandle(void*);
            ]])
            local k32 = ffi.load("kernel32")
            _win_exec = function(cmd)
                local si = ffi.new("STARTUPINFOA[1]")
                si[0].cb = ffi.sizeof("STARTUPINFOA")
                local pi = ffi.new("PROCESS_INFORMATION[1]")
                local CREATE_NO_WINDOW = 0x08000000
                local cmd_buf = ffi.new("char[?]", #cmd + 1, cmd)
                if k32.CreateProcessA(nil, cmd_buf, nil, nil, 0, CREATE_NO_WINDOW, nil, nil, si, pi) ~= 0 then
                    k32.WaitForSingleObject(pi[0].hProcess, 0xFFFFFFFF)
                    k32.CloseHandle(pi[0].hProcess)
                    k32.CloseHandle(pi[0].hThread)
                    return true
                end
                return false
            end
        end
    end
end

-- ============================================================================
-- PLATFORM-SPECIFIC CLIPBOARD FUNCTIONS
-- ============================================================================

--- Copy PNG image to clipboard on macOS.
--- Writes raw PNG bytes to public.png (lossless, no TIFF conversion) so Chrome/Google Docs
--- uploads the smallest possible lossless payload. File URL is appended so Finder can paste.
function copy_to_clipboard_macos(file_path)
    local escaped = file_path:gsub("\\", "\\\\"):gsub('"', '\\"')

    local script = string.format(
        'use framework "Foundation"\n' ..
        'use framework "AppKit"\n' ..
        'set imagePath to "%s"\n' ..
        'set theURL to current application\'s NSURL\'s fileURLWithPath:imagePath\n' ..
        'set theData to current application\'s NSData\'s dataWithContentsOfFile:imagePath\n' ..
        'set thePB to current application\'s NSPasteboard\'s generalPasteboard()\n' ..
        'thePB\'s clearContents()\n' ..
        'thePB\'s addTypes:{"public.png"} owner:(missing value)\n' ..
        'thePB\'s setData:theData forType:"public.png"\n' ..
        'thePB\'s addTypes:{"public.file-url"} owner:(missing value)\n' ..
        'thePB\'s setString:(theURL\'s absoluteString()) forType:"public.file-url"\n',
        escaped
    )

    local tmp = get_temp_dir() .. "/resolve_clipboard.applescript"
    local f = io.open(tmp, "w")
    if not f then return false end
    f:write(script)
    f:close()

    -- Capture stderr only; stdout carries the script return value which is not an error
    local handle = io.popen('/usr/bin/osascript "' .. tmp .. '" 2>&1 1>/dev/null')
    local output = ""
    if handle then
        output = handle:read("*a")
        handle:close()
    end
    os.remove(tmp)

    if output and output ~= "" and output ~= "\n" then
        print("      osascript error: " .. output:gsub("\n", ""))
        return false
    end
    return true
end

--- Copy PNG image to clipboard on Windows using PowerShell.
-- Puts three clipboard formats simultaneously:
--   • PNG (raw bytes) — Chrome/Edge prefer this; lossless and smallest payload
--   • Bitmap — fallback for Word, Paint, and other non-browser apps
--   • FileDrop — for File Explorer (Ctrl+V paste into a folder)
-- Because FileDrop is a file-path reference the PNG must stay on disk until the
-- next run, when stale frame_snapshot_ksf_*.png files are deleted before a new one is written.
-- Runs via wscript.exe (a GUI app) so cmd.exe never flashes on screen.
function copy_to_clipboard_windows(file_path)
    local temp = os.getenv("TEMP") or "C:\\Temp"
    local ps1_path = temp .. "\\dvr_clip.ps1"
    local vbs_path = temp .. "\\dvr_clip.vbs"

    -- Write main PowerShell script.
    -- Reads PNG bytes once; two MemoryStreams avoid seek-position conflicts.
    -- SetData("PNG") is checked first by Chrome/Edge; SetImage is the bitmap fallback.
    -- SetDataObject($true) flushes data so it survives after this process exits.
    local ps = io.open(ps1_path, "w")
    if not ps then return false end
    ps:write(string.format(
        'Add-Type -AssemblyName System.Windows.Forms\n' ..
        'Add-Type -AssemblyName System.Drawing\n' ..
        '$pngBytes = [System.IO.File]::ReadAllBytes("%s")\n' ..
        '$pngStream = New-Object System.IO.MemoryStream(,$pngBytes)\n' ..
        '$img  = [System.Drawing.Image]::FromStream((New-Object System.IO.MemoryStream(,$pngBytes)))\n' ..
        '$data = New-Object System.Windows.Forms.DataObject\n' ..
        '$data.SetData("PNG", $pngStream)\n' ..
        '$data.SetImage($img)\n' ..
        '$fc = New-Object System.Collections.Specialized.StringCollection\n' ..
        '[void]$fc.Add("%s")\n' ..
        '$data.SetFileDropList($fc)\n' ..
        '[System.Windows.Forms.Clipboard]::SetDataObject($data, $true)\n' ..
        '$img.Dispose()\n' ..
        '$pngStream.Dispose()\n',
        file_path,
        file_path
    ))
    ps:close()

    -- Write VBScript that launches PowerShell with window style 0 (fully hidden).
    -- wscript.exe is a GUI subsystem app, so os.execute won't flash a cmd window.
    local vbs = io.open(vbs_path, "w")
    if not vbs then os.remove(ps1_path); return false end
    vbs:write(string.format(
        'CreateObject("WScript.Shell").Run "powershell -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File ""%s""", 0, True\n',
        ps1_path
    ))
    vbs:close()

    local success
    if _win_exec then
        -- CreateProcess with CREATE_NO_WINDOW — no cmd.exe, no flash
        success = _win_exec('wscript //B //Nologo "' .. vbs_path .. '"')
    else
        -- Fallback: still flashes, but works if FFI is unavailable
        local result = os.execute('wscript //B //Nologo "' .. vbs_path .. '"')
        success = result == 0 or result == true
    end

    -- ps1 and vbs have been read by PowerShell/wscript — safe to remove now.
    -- file_path is kept on disk for File Explorer FileDrop paste; deleted at next run.
    os.remove(ps1_path)
    os.remove(vbs_path)
    return success
end

--- Copy PNG image to clipboard on Linux using available tools
function copy_to_clipboard_linux(file_path)
    -- Strategy: Try multiple clipboard tools in order of preference

    -- Option 1: xclip (most common on X11)
    do
        local cmd = string.format(
            'xclip -selection clipboard -t image/png -i "%s" 2>/dev/null',
            file_path
        )
        local result = os.execute(cmd)
        if result == 0 or result == true then
            return true
        end
    end

    -- Option 2: xsel (alternative X11 tool)
    do
        local cmd = string.format(
            'xsel --clipboard --input < "%s" 2>/dev/null',
            file_path
        )
        local result = os.execute(cmd)
        if result == 0 or result == true then
            return true
        end
    end

    -- Option 3: wl-copy (Wayland compositor)
    do
        local cmd = string.format(
            'wl-copy < "%s" 2>/dev/null',
            file_path
        )
        local result = os.execute(cmd)
        if result == 0 or result == true then
            return true
        end
    end

    -- Option 4: Fallback to xdotool + xclip (some systems)
    do
        local cmd = string.format(
            'cat "%s" | xclip -selection clipboard -t image/png 2>/dev/null',
            file_path
        )
        local result = os.execute(cmd)
        if result == 0 or result == true then
            return true
        end
    end

    return false
end

-- ============================================================================
-- MAIN FUNCTION
-- ============================================================================

--- Main function: Export color page frame to clipboard
function export_color_page_to_clipboard()
    print("\n" .. string.rep("=", 70))
    print("DaVinci Resolve: Color Page Frame to Clipboard")
    print("Exporting with Alpha Channel Preservation")
    print(string.rep("=", 70))

    -- Step 1: Detect Operating System
    local os_type = get_os()
    print("\n[1/5] Detecting OS...")
    print("      Detected: " .. os_type)

    if os_type == "Unknown" then
        return false, "Could not detect operating system"
    end

    -- Step 2: Prepare temp file path
    local temp_dir = get_temp_dir()
    local path_sep = get_path_separator()
    local temp_filename = "frame_snapshot_ksf_" .. os.time() .. ".png"
    local temp_path = temp_dir .. path_sep .. temp_filename

    print("\n[2/5] Creating temp file...")

    -- Delete leftover frames from previous runs before writing a new one,
    -- keeping at most one frame_snapshot_ksf_*.png in tmp at any time.
    if os_type == "Windows" then
        os.execute('del /F /Q "' .. temp_dir .. '\\frame_snapshot_ksf_*.png" 2>nul')
    else
        local handle = io.popen('ls "' .. temp_dir .. '"/frame_snapshot_ksf_*.png 2>/dev/null')
        if handle then
            for old_file in handle:lines() do
                os.remove(old_file)
            end
            handle:close()
        end
    end

    print("      Path: " .. temp_path)

    -- Step 3: Export frame
    print("\n[3/5] Exporting current frame as PNG...")
    local export_success, export_result = export_frame_with_alpha(temp_path)

    if not export_success then
        print("      ERROR: " .. export_result)
        return false, export_result
    end
    print("      ✓ Frame exported successfully")

    -- Step 4: Copy to clipboard
    print("\n[4/5] Copying PNG to system clipboard...")
    local clipboard_success = false

    if os_type == "OSX" then
        clipboard_success = copy_to_clipboard_macos(temp_path)
        print("      Using: AppleScript (macOS)")
    elseif os_type == "Windows" then
        clipboard_success = copy_to_clipboard_windows(temp_path)
        print("      Using: PowerShell (Windows)")
    elseif os_type == "Linux" then
        clipboard_success = copy_to_clipboard_linux(temp_path)
        print("      Using: xclip/xsel/wl-copy (Linux)")
    end

    if not clipboard_success then
        print("      ✗ Clipboard operation may have failed")
        print("      Check if required clipboard tools are installed:")
        print("      - macOS: AppleScript (built-in)")
        print("      - Windows: PowerShell (built-in)")
        print("      - Linux: xclip, xsel, or wl-copy")
    else
        if os_type == "Windows" then
            print("      ✓ Copied as image (Google Docs, etc.) + file (File Explorer)")
        else
            print("      ✓ Successfully copied to clipboard")
        end
    end

    -- Step 5: Cleanup
    print("\n[5/5] Cleaning up...")
    if os_type == "Windows" then
        -- FileDrop clipboard format is a file-path reference — the PNG must stay
        -- on disk so File Explorer can paste it. Deleted at the start of the next run.
        print("      File kept for File Explorer paste (cleaned up at next run)")
    elseif os_type == "OSX" and clipboard_success then
        -- Keep the file so Finder can paste it (Cmd+V in Finder reads the file URL).
        -- Deleted at the start of the next run.
        print("      File kept for Finder paste (cleaned up at next run)")
    else
        local cleanup_success = os.remove(temp_path)
        if cleanup_success then
            print("      ✓ Temp file removed")
        else
            print("      ⚠ Could not remove temp file: " .. temp_path)
        end
    end

    -- Final message
    print("\n" .. string.rep("=", 70))
    if clipboard_success then
        print("SUCCESS: Frame with alpha channel copied to clipboard!")
        print("You can now paste the image into another application.")
    else
        print("WARNING: Frame was exported but clipboard copy may have failed.")
        print("The image is available at: " .. temp_path)
    end
    print(string.rep("=", 70) .. "\n")

    return clipboard_success, "Export complete"
end

-- ============================================================================
-- SCRIPT EXECUTION
-- ============================================================================

-- Check if running within DaVinci Resolve (Workspace or Fusion console)
if resolve or GetProject then
    if is_running_from_install() then
        export_color_page_to_clipboard()
    else
        local ok = install_self()
        if ok then
            print("Copy to Clipboard — Installed!")
            print("  Run it from: Workspace > Scripts > Kevshufilms > copy to clipboard")
        else
            print("Copy to Clipboard — Installation failed.")
            print("  Check the error above and try again.")
        end
    end
else
    print("\nERROR: This script must be run from within DaVinci Resolve")
    print("Location: Workspace > Console or Fusion Console")
end
