package com.vgmlr.kerf
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.provider.OpenableColumns
import org.json.JSONObject
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.TimeUnit
object KerfUtils {
val UrlRegex = Regex("""(https?://\S+)""")
val ItemRegex = Regex("""^(\d+)\.""")
const val DEFAULT_PREFIX = "1. "
fun parseKerfText(text: String): Pair<String, String>? {
if (!text.startsWith("Kerf: ")) return null
val doubleNewlineIndex = text.indexOf("\n\n")
val title = if (doubleNewlineIndex != -1) {
text.substring(6, doubleNewlineIndex).trim()
} else {
text.substring(6).trim()
}
val content = if (doubleNewlineIndex != -1) {
text.substring(doubleNewlineIndex + 2).ifBlank { DEFAULT_PREFIX }
} else {
DEFAULT_PREFIX
}
return title to content
}
fun countNumberedItems(content: String): Int {
return content.lines().count { line ->
ItemRegex.find(line.trim()) != null
}
}
fun deleteAndReindex(content: String, lineIndex: Int): String {
val lines = content.lines().toMutableList()
if (lineIndex < 0 || lineIndex >= lines.size) return content
val wasNumbered = ItemRegex.find(lines[lineIndex].trim()) != null
lines.removeAt(lineIndex)
if (lineIndex < lines.size && wasNumbered) {
val nextLine = lines[lineIndex]
val isNextNumbered = ItemRegex.find(nextLine.trim()) != null
if (nextLine.isNotBlank() && !isNextNumbered) {
lines.removeAt(lineIndex)
}
}
if (lineIndex < lines.size && lines[lineIndex].isBlank()) {
lines.removeAt(lineIndex)
}
var currentNum = 1
return lines.joinToString("\n") { line ->
val match = ItemRegex.find(line.trim())
if (match != null) {
val cleanLine = line.trim().replaceFirst(ItemRegex, "").trim()
"$currentNum. $cleanLine".also { currentNum++ }
} else {
line
}
}
}
fun cleanupCache(context: Context) {
try {
context.cacheDir.listFiles { _, name -> name.endsWith(".kerf") }?.forEach { it.delete() }
} catch (_: Exception) {}
}
fun shareReplies(context: Context, title: String, repliesRaw: String?) {
if (repliesRaw == null) return
val json = JSONObject(repliesRaw)
val keys = mutableListOf<Int>()
val it = json.keys()
while (it.hasNext()) {
val key = it.next().toIntOrNull()
if (key != null) keys.add(key)
}
keys.sort()
val list = keys.joinToString("\n\n") { key ->
val text = json.getString(key.toString())
if (text.startsWith("$key. ")) text else "$key. $text"
}
if (list.isNotBlank()) {
val finalContent = if (title.isNotBlank()) "Kerf: Reply: $title\n\n$list" else list
shareNote(context, finalContent)
}
}
fun importNoteFromUri(context: Context, uri: Uri): Pair<String, String>? {
return try {
val contentResolver = context.contentResolver
val fileName = contentResolver.query(uri, null, null, null, null)?.use { cursor ->
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
if (nameIndex != -1 && cursor.moveToFirst()) {
cursor.getString(nameIndex)
} else null
} ?: "Imported List"
val title = fileName.removeSuffix(".kerf").replace("_", " ")
val content = contentResolver.openInputStream(uri)?.bufferedReader()?.use { it.readText() }
if (content != null) title to content else null
} catch (_: Exception) {
null
}
}
fun formatLastSharedDate(timestamp: Long?): String {
if (timestamp == null || timestamp == 0L) return "n/a"
val cal = Calendar.getInstance().apply { timeInMillis = timestamp }
val gYear = cal.get(Calendar.YEAR)
val isBeforeMarch20 = (cal.get(Calendar.MONTH) < Calendar.MARCH) ||
(cal.get(Calendar.MONTH) == Calendar.MARCH && cal.get(Calendar.DAY_OF_MONTH) < 20)
val cycleStartYear = if (isBeforeMarch20) gYear - 1 else gYear
val altYear = cycleStartYear % 100
val cycleStart = Calendar.getInstance().apply {
set(Calendar.YEAR, cycleStartYear); set(Calendar.MONTH, Calendar.MARCH)
set(Calendar.DAY_OF_MONTH, 20); set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0); set(Calendar.SECOND, 0); set(Calendar.MILLISECOND, 0)
}
val diff = timestamp - cycleStart.timeInMillis
val altDayOfYear = TimeUnit.MILLISECONDS.toDays(diff) + 1
val time = SimpleDateFormat("HH:mm", Locale.getDefault()).format(Date(timestamp))
val gregDate = SimpleDateFormat("dd MMM yy", Locale.getDefault()).format(Date(timestamp))
return "$time $altDayOfYear 0$altYear ($gregDate)"
}
}
fun shareNote(context: Context, content: String) {
val shareIntent = Intent().apply {
action = Intent.ACTION_SEND
type = "text/plain"
putExtra(Intent.EXTRA_TEXT, content)
}
context.startActivity(Intent.createChooser(shareIntent, "Share List"))
}