if(NOT DEFINED PP_SHADER_DIR)
    message(FATAL_ERROR "PP_SHADER_DIR is required")
endif()

file(REAL_PATH "${PP_SHADER_DIR}" pp_shader_dir)
if(NOT IS_DIRECTORY "${pp_shader_dir}")
    message(FATAL_ERROR "Shader directory does not exist: ${pp_shader_dir}")
endif()

file(GLOB_RECURSE pp_shader_files
    "${pp_shader_dir}/*.glsl")

if(NOT pp_shader_files)
    message(FATAL_ERROR "No shader files found under: ${pp_shader_dir}")
endif()

set(pp_shader_errors "")
set(pp_top_level_count 0)
set(pp_include_count 0)

foreach(pp_shader_file IN LISTS pp_shader_files)
    file(RELATIVE_PATH pp_shader_rel "${pp_shader_dir}" "${pp_shader_file}")
    file(READ "${pp_shader_file}" pp_shader_contents)

    string(REGEX MATCHALL "#[ \t]*include[ \t]+\"[^\"]+\"" pp_include_lines "${pp_shader_contents}")
    foreach(pp_include_line IN LISTS pp_include_lines)
        string(REGEX REPLACE ".*\"([^\"]+)\".*" "\\1" pp_include_path "${pp_include_line}")
        if(pp_include_path MATCHES "^/")
            list(APPEND pp_shader_errors "${pp_shader_rel}: include path must be relative: ${pp_include_path}")
        endif()
        if(pp_include_path MATCHES "^[A-Za-z]:")
            list(APPEND pp_shader_errors "${pp_shader_rel}: include path must not be drive-absolute: ${pp_include_path}")
        endif()
        if(pp_include_path MATCHES "\\.\\.")
            list(APPEND pp_shader_errors "${pp_shader_rel}: include path must not traverse parent directories: ${pp_include_path}")
        endif()
        if(NOT EXISTS "${pp_shader_dir}/${pp_include_path}")
            list(APPEND pp_shader_errors "${pp_shader_rel}: missing include: ${pp_include_path}")
        endif()
    endforeach()

    if(pp_shader_rel MATCHES "^include/")
        math(EXPR pp_include_count "${pp_include_count} + 1")
        if(pp_shader_contents MATCHES "\\[\\[(vertex|fragment)\\]\\]")
            list(APPEND pp_shader_errors "${pp_shader_rel}: include shaders must not declare stage markers")
        endif()
    else()
        math(EXPR pp_top_level_count "${pp_top_level_count} + 1")

        string(REGEX MATCHALL "\\[\\[vertex\\]\\]" pp_vertex_markers "${pp_shader_contents}")
        string(REGEX MATCHALL "\\[\\[fragment\\]\\]" pp_fragment_markers "${pp_shader_contents}")
        list(LENGTH pp_vertex_markers pp_vertex_count)
        list(LENGTH pp_fragment_markers pp_fragment_count)

        if(NOT pp_vertex_count EQUAL 1)
            list(APPEND pp_shader_errors "${pp_shader_rel}: expected exactly one [[vertex]] marker")
        endif()
        if(NOT pp_fragment_count EQUAL 1)
            list(APPEND pp_shader_errors "${pp_shader_rel}: expected exactly one [[fragment]] marker")
        endif()

        string(FIND "${pp_shader_contents}" "[[vertex]]" pp_vertex_pos)
        string(FIND "${pp_shader_contents}" "[[fragment]]" pp_fragment_pos)
        if(pp_vertex_pos GREATER_EQUAL 0 AND pp_fragment_pos GREATER_EQUAL 0 AND NOT pp_vertex_pos LESS pp_fragment_pos)
            list(APPEND pp_shader_errors "${pp_shader_rel}: [[vertex]] marker must appear before [[fragment]]")
        endif()
    endif()
endforeach()

if(pp_shader_errors)
    list(JOIN pp_shader_errors "\n" pp_shader_error_text)
    message(FATAL_ERROR "Shader validation failed:\n${pp_shader_error_text}")
endif()

message(STATUS "Validated ${pp_top_level_count} shader programs and ${pp_include_count} shader includes under ${pp_shader_dir}")
